From 916a8b9fed2593cb3fa8910b5bba1b4ac7d9e77a Mon Sep 17 00:00:00 2001 From: creating2morrow Date: Sun, 30 Apr 2023 11:55:41 -0400 Subject: [PATCH] init --- .gitignore | 9 + Cargo.lock | 4115 ++++++++++++++ Cargo.toml | 15 + README.md | 59 + docs/curl.md | 67 + docs/man.md | 54 + nevmes-auth/.gitignore | 6 + nevmes-auth/Cargo.lock | 2600 +++++++++ nevmes-auth/Cargo.toml | 12 + nevmes-auth/src/controller.rs | 17 + nevmes-auth/src/lib.rs | 2 + nevmes-auth/src/main.rs | 19 + nevmes-contact/.gitignore | 6 + nevmes-contact/Cargo.lock | 2600 +++++++++ nevmes-contact/Cargo.toml | 12 + nevmes-contact/src/controller.rs | 33 + nevmes-contact/src/lib.rs | 2 + nevmes-contact/src/main.rs | 21 + nevmes-core/Cargo.lock | 2590 +++++++++ nevmes-core/Cargo.toml | 30 + nevmes-core/src/args.rs | 127 + nevmes-core/src/auth.rs | 221 + nevmes-core/src/contact.rs | 154 + nevmes-core/src/db.rs | 61 + nevmes-core/src/gpg.rs | 169 + nevmes-core/src/i2p.rs | 184 + nevmes-core/src/lib.rs | 18 + nevmes-core/src/message.rs | 248 + nevmes-core/src/models.rs | 208 + nevmes-core/src/monero.rs | 805 +++ nevmes-core/src/proof.rs | 206 + nevmes-core/src/reqres.rs | 843 +++ nevmes-core/src/user.rs | 47 + nevmes-core/src/utils.rs | 366 ++ nevmes-gui/.gitignore | 4 + nevmes-gui/Cargo.lock | 4752 +++++++++++++++++ nevmes-gui/Cargo.toml | 60 + nevmes-gui/LICENSE-APACHE | 201 + nevmes-gui/LICENSE-MIT | 25 + nevmes-gui/README.md | 4 + nevmes-gui/assets/i2p.png | Bin 0 -> 10537 bytes nevmes-gui/assets/qr.png | Bin 0 -> 12776 bytes nevmes-gui/assets/xmr.png | Bin 0 -> 2893 bytes nevmes-gui/crates/ecolor/CHANGELOG.md | 13 + nevmes-gui/crates/ecolor/Cargo.toml | 50 + nevmes-gui/crates/ecolor/README.md | 11 + nevmes-gui/crates/ecolor/src/cint_impl.rs | 161 + nevmes-gui/crates/ecolor/src/color32.rs | 216 + .../crates/ecolor/src/hex_color_macro.rs | 39 + nevmes-gui/crates/ecolor/src/hsva.rs | 231 + nevmes-gui/crates/ecolor/src/hsva_gamma.rs | 66 + nevmes-gui/crates/ecolor/src/lib.rs | 173 + nevmes-gui/crates/ecolor/src/rgba.rs | 266 + nevmes-gui/crates/eframe/CHANGELOG.md | 229 + nevmes-gui/crates/eframe/Cargo.toml | 169 + nevmes-gui/crates/eframe/README.md | 63 + nevmes-gui/crates/eframe/src/epi.rs | 1065 ++++ nevmes-gui/crates/eframe/src/lib.rs | 262 + .../eframe/src/native/epi_integration.rs | 568 ++ .../crates/eframe/src/native/file_storage.rs | 117 + nevmes-gui/crates/eframe/src/native/mod.rs | 6 + nevmes-gui/crates/eframe/src/native/run.rs | 1423 +++++ nevmes-gui/crates/eframe/src/web/backend.rs | 585 ++ nevmes-gui/crates/eframe/src/web/events.rs | 538 ++ nevmes-gui/crates/eframe/src/web/input.rs | 217 + nevmes-gui/crates/eframe/src/web/mod.rs | 258 + .../crates/eframe/src/web/screen_reader.rs | 49 + nevmes-gui/crates/eframe/src/web/storage.rs | 43 + .../crates/eframe/src/web/text_agent.rs | 225 + .../crates/eframe/src/web/web_painter.rs | 29 + .../crates/eframe/src/web/web_painter_glow.rs | 184 + .../crates/eframe/src/web/web_painter_wgpu.rs | 282 + nevmes-gui/crates/egui-wgpu/CHANGELOG.md | 37 + nevmes-gui/crates/egui-wgpu/Cargo.toml | 56 + nevmes-gui/crates/egui-wgpu/README.md | 10 + nevmes-gui/crates/egui-wgpu/src/egui.wgsl | 91 + nevmes-gui/crates/egui-wgpu/src/lib.rs | 155 + nevmes-gui/crates/egui-wgpu/src/renderer.rs | 931 ++++ nevmes-gui/crates/egui-wgpu/src/winit.rs | 418 ++ nevmes-gui/crates/egui-winit/CHANGELOG.md | 64 + nevmes-gui/crates/egui-winit/Cargo.toml | 76 + nevmes-gui/crates/egui-winit/README.md | 10 + nevmes-gui/crates/egui-winit/src/clipboard.rs | 146 + nevmes-gui/crates/egui-winit/src/lib.rs | 920 ++++ .../crates/egui-winit/src/window_settings.rs | 144 + nevmes-gui/crates/egui/Cargo.toml | 84 + nevmes-gui/crates/egui/README.md | 2 + nevmes-gui/crates/egui/examples/README.md | 7 + .../crates/egui/src/animation_manager.rs | 114 + nevmes-gui/crates/egui/src/containers/area.rs | 514 ++ .../egui/src/containers/collapsing_header.rs | 681 +++ .../crates/egui/src/containers/combo_box.rs | 429 ++ .../crates/egui/src/containers/frame.rs | 288 + nevmes-gui/crates/egui/src/containers/mod.rs | 25 + .../crates/egui/src/containers/panel.rs | 1064 ++++ .../crates/egui/src/containers/popup.rs | 378 ++ .../crates/egui/src/containers/resize.rs | 351 ++ .../crates/egui/src/containers/scroll_area.rs | 924 ++++ .../crates/egui/src/containers/window.rs | 982 ++++ nevmes-gui/crates/egui/src/context.rs | 1799 +++++++ nevmes-gui/crates/egui/src/data/input.rs | 910 ++++ nevmes-gui/crates/egui/src/data/mod.rs | 4 + nevmes-gui/crates/egui/src/data/output.rs | 629 +++ nevmes-gui/crates/egui/src/frame_state.rs | 173 + nevmes-gui/crates/egui/src/grid.rs | 408 ++ nevmes-gui/crates/egui/src/gui_zoom.rs | 117 + nevmes-gui/crates/egui/src/id.rs | 175 + nevmes-gui/crates/egui/src/input_state.rs | 1018 ++++ .../egui/src/input_state/touch_state.rs | 339 ++ nevmes-gui/crates/egui/src/introspection.rs | 198 + nevmes-gui/crates/egui/src/layers.rs | 201 + nevmes-gui/crates/egui/src/layout.rs | 841 +++ nevmes-gui/crates/egui/src/lib.rs | 573 ++ nevmes-gui/crates/egui/src/memory.rs | 647 +++ nevmes-gui/crates/egui/src/menu.rs | 690 +++ nevmes-gui/crates/egui/src/os.rs | 76 + nevmes-gui/crates/egui/src/painter.rs | 455 ++ nevmes-gui/crates/egui/src/placer.rs | 279 + nevmes-gui/crates/egui/src/response.rs | 819 +++ nevmes-gui/crates/egui/src/sense.rs | 79 + nevmes-gui/crates/egui/src/style.rs | 1507 ++++++ nevmes-gui/crates/egui/src/ui.rs | 2233 ++++++++ nevmes-gui/crates/egui/src/util/cache.rs | 164 + .../crates/egui/src/util/fixed_cache.rs | 41 + .../crates/egui/src/util/id_type_map.rs | 720 +++ nevmes-gui/crates/egui/src/util/mod.rs | 11 + nevmes-gui/crates/egui/src/util/undoer.rs | 172 + nevmes-gui/crates/egui/src/widget_text.rs | 725 +++ nevmes-gui/crates/egui/src/widgets/button.rs | 571 ++ .../crates/egui/src/widgets/color_picker.rs | 446 ++ .../crates/egui/src/widgets/drag_value.rs | 662 +++ .../crates/egui/src/widgets/hyperlink.rs | 131 + nevmes-gui/crates/egui/src/widgets/image.rs | 141 + nevmes-gui/crates/egui/src/widgets/label.rs | 199 + nevmes-gui/crates/egui/src/widgets/mod.rs | 155 + .../crates/egui/src/widgets/plot/items/bar.rs | 195 + .../egui/src/widgets/plot/items/box_elem.rs | 293 + .../crates/egui/src/widgets/plot/items/mod.rs | 1749 ++++++ .../egui/src/widgets/plot/items/rect_elem.rs | 63 + .../egui/src/widgets/plot/items/values.rs | 432 ++ .../crates/egui/src/widgets/plot/legend.rs | 258 + .../crates/egui/src/widgets/plot/mod.rs | 1667 ++++++ .../crates/egui/src/widgets/plot/transform.rs | 383 ++ .../crates/egui/src/widgets/progress_bar.rs | 159 + .../crates/egui/src/widgets/selected_label.rs | 80 + .../crates/egui/src/widgets/separator.rs | 124 + nevmes-gui/crates/egui/src/widgets/slider.rs | 1012 ++++ nevmes-gui/crates/egui/src/widgets/spinner.rs | 67 + .../egui/src/widgets/text_edit/builder.rs | 1608 ++++++ .../src/widgets/text_edit/cursor_range.rs | 150 + .../crates/egui/src/widgets/text_edit/mod.rs | 10 + .../egui/src/widgets/text_edit/output.rs | 24 + .../egui/src/widgets/text_edit/state.rs | 85 + .../egui/src/widgets/text_edit/text_buffer.rs | 127 + nevmes-gui/crates/egui_extras/CHANGELOG.md | 44 + nevmes-gui/crates/egui_extras/Cargo.toml | 68 + nevmes-gui/crates/egui_extras/README.md | 9 + .../egui_extras/src/datepicker/button.rs | 141 + .../crates/egui_extras/src/datepicker/mod.rs | 34 + .../egui_extras/src/datepicker/popup.rs | 433 ++ nevmes-gui/crates/egui_extras/src/image.rs | 264 + nevmes-gui/crates/egui_extras/src/layout.rs | 171 + nevmes-gui/crates/egui_extras/src/lib.rs | 54 + nevmes-gui/crates/egui_extras/src/sizing.rs | 177 + nevmes-gui/crates/egui_extras/src/strip.rs | 197 + nevmes-gui/crates/egui_extras/src/table.rs | 1063 ++++ nevmes-gui/crates/egui_glium/CHANGELOG.md | 122 + nevmes-gui/crates/egui_glium/Cargo.toml | 58 + nevmes-gui/crates/egui_glium/README.md | 21 + .../egui_glium/examples/native_texture.rs | 139 + .../crates/egui_glium/examples/pure_glium.rs | 108 + nevmes-gui/crates/egui_glium/src/lib.rs | 99 + nevmes-gui/crates/egui_glium/src/painter.rs | 381 ++ .../egui_glium/src/shader/fragment_100es.glsl | 38 + .../egui_glium/src/shader/fragment_120.glsl | 31 + .../egui_glium/src/shader/fragment_140.glsl | 32 + .../egui_glium/src/shader/fragment_300es.glsl | 32 + .../egui_glium/src/shader/vertex_100es.glsl | 19 + .../egui_glium/src/shader/vertex_120.glsl | 18 + .../egui_glium/src/shader/vertex_140.glsl | 18 + .../egui_glium/src/shader/vertex_300es.glsl | 19 + nevmes-gui/crates/egui_glow/CHANGELOG.md | 64 + nevmes-gui/crates/egui_glow/Cargo.toml | 79 + nevmes-gui/crates/egui_glow/README.md | 26 + .../crates/egui_glow/examples/pure_glow.rs | 255 + nevmes-gui/crates/egui_glow/src/lib.rs | 133 + nevmes-gui/crates/egui_glow/src/misc_util.rs | 40 + nevmes-gui/crates/egui_glow/src/painter.rs | 748 +++ .../crates/egui_glow/src/shader/fragment.glsl | 41 + .../crates/egui_glow/src/shader/vertex.glsl | 30 + .../crates/egui_glow/src/shader_version.rs | 104 + nevmes-gui/crates/egui_glow/src/vao.rs | 157 + nevmes-gui/crates/egui_glow/src/winit.rs | 92 + nevmes-gui/crates/egui_web/CHANGELOG.md | 120 + nevmes-gui/crates/egui_web/README.md | 1 + nevmes-gui/crates/emath/Cargo.toml | 44 + nevmes-gui/crates/emath/README.md | 11 + nevmes-gui/crates/emath/src/align.rs | 265 + nevmes-gui/crates/emath/src/history.rs | 229 + nevmes-gui/crates/emath/src/lib.rs | 391 ++ nevmes-gui/crates/emath/src/numeric.rs | 74 + nevmes-gui/crates/emath/src/pos2.rs | 279 + nevmes-gui/crates/emath/src/rect.rs | 537 ++ nevmes-gui/crates/emath/src/rect_transform.rs | 83 + nevmes-gui/crates/emath/src/rot2.rs | 198 + nevmes-gui/crates/emath/src/smart_aim.rs | 157 + nevmes-gui/crates/emath/src/vec2.rs | 463 ++ nevmes-gui/crates/epaint/CHANGELOG.md | 81 + nevmes-gui/crates/epaint/Cargo.toml | 105 + nevmes-gui/crates/epaint/README.md | 11 + nevmes-gui/crates/epaint/benches/benchmark.rs | 81 + .../crates/epaint/fonts/Hack-Regular.ttf | Bin 0 -> 309408 bytes .../crates/epaint/fonts/Hack-Regular.txt | 31 + .../crates/epaint/fonts/NotoEmoji-Regular.ttf | Bin 0 -> 418804 bytes nevmes-gui/crates/epaint/fonts/OFL.txt | 92 + nevmes-gui/crates/epaint/fonts/UFL.txt | 96 + .../crates/epaint/fonts/Ubuntu-Light.ttf | Bin 0 -> 361676 bytes .../fonts/emoji-icon-font-mit-license.txt | 9 + .../crates/epaint/fonts/emoji-icon-font.ttf | Bin 0 -> 317864 bytes nevmes-gui/crates/epaint/fonts/list_fonts.py | 32 + nevmes-gui/crates/epaint/src/bezier.rs | 1124 ++++ nevmes-gui/crates/epaint/src/image.rs | 326 ++ nevmes-gui/crates/epaint/src/lib.rs | 160 + nevmes-gui/crates/epaint/src/mesh.rs | 316 ++ nevmes-gui/crates/epaint/src/mutex.rs | 579 ++ nevmes-gui/crates/epaint/src/shadow.rs | 87 + nevmes-gui/crates/epaint/src/shape.rs | 846 +++ .../crates/epaint/src/shape_transform.rs | 58 + nevmes-gui/crates/epaint/src/stats.rs | 245 + nevmes-gui/crates/epaint/src/stroke.rs | 60 + nevmes-gui/crates/epaint/src/tessellator.rs | 1657 ++++++ nevmes-gui/crates/epaint/src/text/cursor.rs | 122 + nevmes-gui/crates/epaint/src/text/font.rs | 486 ++ nevmes-gui/crates/epaint/src/text/fonts.rs | 789 +++ nevmes-gui/crates/epaint/src/text/mod.rs | 19 + .../crates/epaint/src/text/text_layout.rs | 858 +++ .../epaint/src/text/text_layout_types.rs | 911 ++++ nevmes-gui/crates/epaint/src/texture_atlas.rs | 252 + .../crates/epaint/src/texture_handle.rs | 122 + nevmes-gui/crates/epaint/src/textures.rs | 257 + nevmes-gui/crates/epaint/src/util/mod.rs | 16 + .../crates/epaint/src/util/ordered_float.rs | 130 + nevmes-gui/src/apps/address_book.rs | 680 +++ nevmes-gui/src/apps/home.rs | 324 ++ nevmes-gui/src/apps/lock_screen.rs | 73 + nevmes-gui/src/apps/mailbox.rs | 147 + nevmes-gui/src/apps/mod.rs | 14 + nevmes-gui/src/apps/settings.rs | 46 + nevmes-gui/src/apps/wallet.rs | 147 + nevmes-gui/src/lib.rs | 29 + nevmes-gui/src/login.rs | 47 + nevmes-gui/src/main.rs | 38 + nevmes-gui/src/wrap_app.rs | 303 ++ nevmes-message/Cargo.lock | 2600 +++++++++ nevmes-message/Cargo.toml | 12 + nevmes-message/src/controller.rs | 31 + nevmes-message/src/lib.rs | 2 + nevmes-message/src/main.rs | 21 + scripts/build_all_and_run.sh | 7 + scripts/cleanall.sh | 8 + src/controller.rs | 68 + src/lib.rs | 2 + src/main.rs | 25 + 263 files changed, 87198 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 docs/curl.md create mode 100644 docs/man.md create mode 100644 nevmes-auth/.gitignore create mode 100644 nevmes-auth/Cargo.lock create mode 100644 nevmes-auth/Cargo.toml create mode 100644 nevmes-auth/src/controller.rs create mode 100644 nevmes-auth/src/lib.rs create mode 100644 nevmes-auth/src/main.rs create mode 100644 nevmes-contact/.gitignore create mode 100644 nevmes-contact/Cargo.lock create mode 100644 nevmes-contact/Cargo.toml create mode 100644 nevmes-contact/src/controller.rs create mode 100644 nevmes-contact/src/lib.rs create mode 100644 nevmes-contact/src/main.rs create mode 100644 nevmes-core/Cargo.lock create mode 100644 nevmes-core/Cargo.toml create mode 100644 nevmes-core/src/args.rs create mode 100644 nevmes-core/src/auth.rs create mode 100644 nevmes-core/src/contact.rs create mode 100644 nevmes-core/src/db.rs create mode 100644 nevmes-core/src/gpg.rs create mode 100644 nevmes-core/src/i2p.rs create mode 100644 nevmes-core/src/lib.rs create mode 100644 nevmes-core/src/message.rs create mode 100644 nevmes-core/src/models.rs create mode 100644 nevmes-core/src/monero.rs create mode 100644 nevmes-core/src/proof.rs create mode 100644 nevmes-core/src/reqres.rs create mode 100644 nevmes-core/src/user.rs create mode 100644 nevmes-core/src/utils.rs create mode 100755 nevmes-gui/.gitignore create mode 100644 nevmes-gui/Cargo.lock create mode 100644 nevmes-gui/Cargo.toml create mode 100644 nevmes-gui/LICENSE-APACHE create mode 100644 nevmes-gui/LICENSE-MIT create mode 100644 nevmes-gui/README.md create mode 100644 nevmes-gui/assets/i2p.png create mode 100644 nevmes-gui/assets/qr.png create mode 100644 nevmes-gui/assets/xmr.png create mode 100644 nevmes-gui/crates/ecolor/CHANGELOG.md create mode 100644 nevmes-gui/crates/ecolor/Cargo.toml create mode 100644 nevmes-gui/crates/ecolor/README.md create mode 100644 nevmes-gui/crates/ecolor/src/cint_impl.rs create mode 100644 nevmes-gui/crates/ecolor/src/color32.rs create mode 100644 nevmes-gui/crates/ecolor/src/hex_color_macro.rs create mode 100644 nevmes-gui/crates/ecolor/src/hsva.rs create mode 100644 nevmes-gui/crates/ecolor/src/hsva_gamma.rs create mode 100644 nevmes-gui/crates/ecolor/src/lib.rs create mode 100644 nevmes-gui/crates/ecolor/src/rgba.rs create mode 100644 nevmes-gui/crates/eframe/CHANGELOG.md create mode 100644 nevmes-gui/crates/eframe/Cargo.toml create mode 100644 nevmes-gui/crates/eframe/README.md create mode 100644 nevmes-gui/crates/eframe/src/epi.rs create mode 100644 nevmes-gui/crates/eframe/src/lib.rs create mode 100644 nevmes-gui/crates/eframe/src/native/epi_integration.rs create mode 100644 nevmes-gui/crates/eframe/src/native/file_storage.rs create mode 100644 nevmes-gui/crates/eframe/src/native/mod.rs create mode 100644 nevmes-gui/crates/eframe/src/native/run.rs create mode 100644 nevmes-gui/crates/eframe/src/web/backend.rs create mode 100644 nevmes-gui/crates/eframe/src/web/events.rs create mode 100644 nevmes-gui/crates/eframe/src/web/input.rs create mode 100644 nevmes-gui/crates/eframe/src/web/mod.rs create mode 100644 nevmes-gui/crates/eframe/src/web/screen_reader.rs create mode 100644 nevmes-gui/crates/eframe/src/web/storage.rs create mode 100644 nevmes-gui/crates/eframe/src/web/text_agent.rs create mode 100644 nevmes-gui/crates/eframe/src/web/web_painter.rs create mode 100644 nevmes-gui/crates/eframe/src/web/web_painter_glow.rs create mode 100644 nevmes-gui/crates/eframe/src/web/web_painter_wgpu.rs create mode 100644 nevmes-gui/crates/egui-wgpu/CHANGELOG.md create mode 100644 nevmes-gui/crates/egui-wgpu/Cargo.toml create mode 100644 nevmes-gui/crates/egui-wgpu/README.md create mode 100644 nevmes-gui/crates/egui-wgpu/src/egui.wgsl create mode 100644 nevmes-gui/crates/egui-wgpu/src/lib.rs create mode 100644 nevmes-gui/crates/egui-wgpu/src/renderer.rs create mode 100644 nevmes-gui/crates/egui-wgpu/src/winit.rs create mode 100644 nevmes-gui/crates/egui-winit/CHANGELOG.md create mode 100644 nevmes-gui/crates/egui-winit/Cargo.toml create mode 100644 nevmes-gui/crates/egui-winit/README.md create mode 100644 nevmes-gui/crates/egui-winit/src/clipboard.rs create mode 100644 nevmes-gui/crates/egui-winit/src/lib.rs create mode 100644 nevmes-gui/crates/egui-winit/src/window_settings.rs create mode 100644 nevmes-gui/crates/egui/Cargo.toml create mode 100644 nevmes-gui/crates/egui/README.md create mode 100644 nevmes-gui/crates/egui/examples/README.md create mode 100644 nevmes-gui/crates/egui/src/animation_manager.rs create mode 100644 nevmes-gui/crates/egui/src/containers/area.rs create mode 100644 nevmes-gui/crates/egui/src/containers/collapsing_header.rs create mode 100644 nevmes-gui/crates/egui/src/containers/combo_box.rs create mode 100644 nevmes-gui/crates/egui/src/containers/frame.rs create mode 100644 nevmes-gui/crates/egui/src/containers/mod.rs create mode 100644 nevmes-gui/crates/egui/src/containers/panel.rs create mode 100644 nevmes-gui/crates/egui/src/containers/popup.rs create mode 100644 nevmes-gui/crates/egui/src/containers/resize.rs create mode 100644 nevmes-gui/crates/egui/src/containers/scroll_area.rs create mode 100644 nevmes-gui/crates/egui/src/containers/window.rs create mode 100644 nevmes-gui/crates/egui/src/context.rs create mode 100644 nevmes-gui/crates/egui/src/data/input.rs create mode 100644 nevmes-gui/crates/egui/src/data/mod.rs create mode 100644 nevmes-gui/crates/egui/src/data/output.rs create mode 100644 nevmes-gui/crates/egui/src/frame_state.rs create mode 100644 nevmes-gui/crates/egui/src/grid.rs create mode 100644 nevmes-gui/crates/egui/src/gui_zoom.rs create mode 100644 nevmes-gui/crates/egui/src/id.rs create mode 100644 nevmes-gui/crates/egui/src/input_state.rs create mode 100644 nevmes-gui/crates/egui/src/input_state/touch_state.rs create mode 100644 nevmes-gui/crates/egui/src/introspection.rs create mode 100644 nevmes-gui/crates/egui/src/layers.rs create mode 100644 nevmes-gui/crates/egui/src/layout.rs create mode 100644 nevmes-gui/crates/egui/src/lib.rs create mode 100644 nevmes-gui/crates/egui/src/memory.rs create mode 100644 nevmes-gui/crates/egui/src/menu.rs create mode 100644 nevmes-gui/crates/egui/src/os.rs create mode 100644 nevmes-gui/crates/egui/src/painter.rs create mode 100644 nevmes-gui/crates/egui/src/placer.rs create mode 100644 nevmes-gui/crates/egui/src/response.rs create mode 100644 nevmes-gui/crates/egui/src/sense.rs create mode 100644 nevmes-gui/crates/egui/src/style.rs create mode 100644 nevmes-gui/crates/egui/src/ui.rs create mode 100644 nevmes-gui/crates/egui/src/util/cache.rs create mode 100644 nevmes-gui/crates/egui/src/util/fixed_cache.rs create mode 100644 nevmes-gui/crates/egui/src/util/id_type_map.rs create mode 100644 nevmes-gui/crates/egui/src/util/mod.rs create mode 100644 nevmes-gui/crates/egui/src/util/undoer.rs create mode 100644 nevmes-gui/crates/egui/src/widget_text.rs create mode 100644 nevmes-gui/crates/egui/src/widgets/button.rs create mode 100644 nevmes-gui/crates/egui/src/widgets/color_picker.rs create mode 100644 nevmes-gui/crates/egui/src/widgets/drag_value.rs create mode 100644 nevmes-gui/crates/egui/src/widgets/hyperlink.rs create mode 100644 nevmes-gui/crates/egui/src/widgets/image.rs create mode 100644 nevmes-gui/crates/egui/src/widgets/label.rs create mode 100644 nevmes-gui/crates/egui/src/widgets/mod.rs create mode 100644 nevmes-gui/crates/egui/src/widgets/plot/items/bar.rs create mode 100644 nevmes-gui/crates/egui/src/widgets/plot/items/box_elem.rs create mode 100644 nevmes-gui/crates/egui/src/widgets/plot/items/mod.rs create mode 100644 nevmes-gui/crates/egui/src/widgets/plot/items/rect_elem.rs create mode 100644 nevmes-gui/crates/egui/src/widgets/plot/items/values.rs create mode 100644 nevmes-gui/crates/egui/src/widgets/plot/legend.rs create mode 100644 nevmes-gui/crates/egui/src/widgets/plot/mod.rs create mode 100644 nevmes-gui/crates/egui/src/widgets/plot/transform.rs create mode 100644 nevmes-gui/crates/egui/src/widgets/progress_bar.rs create mode 100644 nevmes-gui/crates/egui/src/widgets/selected_label.rs create mode 100644 nevmes-gui/crates/egui/src/widgets/separator.rs create mode 100644 nevmes-gui/crates/egui/src/widgets/slider.rs create mode 100644 nevmes-gui/crates/egui/src/widgets/spinner.rs create mode 100644 nevmes-gui/crates/egui/src/widgets/text_edit/builder.rs create mode 100644 nevmes-gui/crates/egui/src/widgets/text_edit/cursor_range.rs create mode 100644 nevmes-gui/crates/egui/src/widgets/text_edit/mod.rs create mode 100644 nevmes-gui/crates/egui/src/widgets/text_edit/output.rs create mode 100644 nevmes-gui/crates/egui/src/widgets/text_edit/state.rs create mode 100644 nevmes-gui/crates/egui/src/widgets/text_edit/text_buffer.rs create mode 100644 nevmes-gui/crates/egui_extras/CHANGELOG.md create mode 100644 nevmes-gui/crates/egui_extras/Cargo.toml create mode 100644 nevmes-gui/crates/egui_extras/README.md create mode 100644 nevmes-gui/crates/egui_extras/src/datepicker/button.rs create mode 100644 nevmes-gui/crates/egui_extras/src/datepicker/mod.rs create mode 100644 nevmes-gui/crates/egui_extras/src/datepicker/popup.rs create mode 100644 nevmes-gui/crates/egui_extras/src/image.rs create mode 100644 nevmes-gui/crates/egui_extras/src/layout.rs create mode 100644 nevmes-gui/crates/egui_extras/src/lib.rs create mode 100644 nevmes-gui/crates/egui_extras/src/sizing.rs create mode 100644 nevmes-gui/crates/egui_extras/src/strip.rs create mode 100644 nevmes-gui/crates/egui_extras/src/table.rs create mode 100644 nevmes-gui/crates/egui_glium/CHANGELOG.md create mode 100644 nevmes-gui/crates/egui_glium/Cargo.toml create mode 100644 nevmes-gui/crates/egui_glium/README.md create mode 100644 nevmes-gui/crates/egui_glium/examples/native_texture.rs create mode 100644 nevmes-gui/crates/egui_glium/examples/pure_glium.rs create mode 100644 nevmes-gui/crates/egui_glium/src/lib.rs create mode 100644 nevmes-gui/crates/egui_glium/src/painter.rs create mode 100644 nevmes-gui/crates/egui_glium/src/shader/fragment_100es.glsl create mode 100644 nevmes-gui/crates/egui_glium/src/shader/fragment_120.glsl create mode 100644 nevmes-gui/crates/egui_glium/src/shader/fragment_140.glsl create mode 100644 nevmes-gui/crates/egui_glium/src/shader/fragment_300es.glsl create mode 100644 nevmes-gui/crates/egui_glium/src/shader/vertex_100es.glsl create mode 100644 nevmes-gui/crates/egui_glium/src/shader/vertex_120.glsl create mode 100644 nevmes-gui/crates/egui_glium/src/shader/vertex_140.glsl create mode 100644 nevmes-gui/crates/egui_glium/src/shader/vertex_300es.glsl create mode 100644 nevmes-gui/crates/egui_glow/CHANGELOG.md create mode 100644 nevmes-gui/crates/egui_glow/Cargo.toml create mode 100644 nevmes-gui/crates/egui_glow/README.md create mode 100644 nevmes-gui/crates/egui_glow/examples/pure_glow.rs create mode 100644 nevmes-gui/crates/egui_glow/src/lib.rs create mode 100644 nevmes-gui/crates/egui_glow/src/misc_util.rs create mode 100644 nevmes-gui/crates/egui_glow/src/painter.rs create mode 100644 nevmes-gui/crates/egui_glow/src/shader/fragment.glsl create mode 100644 nevmes-gui/crates/egui_glow/src/shader/vertex.glsl create mode 100644 nevmes-gui/crates/egui_glow/src/shader_version.rs create mode 100644 nevmes-gui/crates/egui_glow/src/vao.rs create mode 100644 nevmes-gui/crates/egui_glow/src/winit.rs create mode 100644 nevmes-gui/crates/egui_web/CHANGELOG.md create mode 100644 nevmes-gui/crates/egui_web/README.md create mode 100644 nevmes-gui/crates/emath/Cargo.toml create mode 100644 nevmes-gui/crates/emath/README.md create mode 100644 nevmes-gui/crates/emath/src/align.rs create mode 100644 nevmes-gui/crates/emath/src/history.rs create mode 100644 nevmes-gui/crates/emath/src/lib.rs create mode 100644 nevmes-gui/crates/emath/src/numeric.rs create mode 100644 nevmes-gui/crates/emath/src/pos2.rs create mode 100644 nevmes-gui/crates/emath/src/rect.rs create mode 100644 nevmes-gui/crates/emath/src/rect_transform.rs create mode 100644 nevmes-gui/crates/emath/src/rot2.rs create mode 100644 nevmes-gui/crates/emath/src/smart_aim.rs create mode 100644 nevmes-gui/crates/emath/src/vec2.rs create mode 100644 nevmes-gui/crates/epaint/CHANGELOG.md create mode 100644 nevmes-gui/crates/epaint/Cargo.toml create mode 100644 nevmes-gui/crates/epaint/README.md create mode 100644 nevmes-gui/crates/epaint/benches/benchmark.rs create mode 100644 nevmes-gui/crates/epaint/fonts/Hack-Regular.ttf create mode 100644 nevmes-gui/crates/epaint/fonts/Hack-Regular.txt create mode 100644 nevmes-gui/crates/epaint/fonts/NotoEmoji-Regular.ttf create mode 100644 nevmes-gui/crates/epaint/fonts/OFL.txt create mode 100755 nevmes-gui/crates/epaint/fonts/UFL.txt create mode 100755 nevmes-gui/crates/epaint/fonts/Ubuntu-Light.ttf create mode 100644 nevmes-gui/crates/epaint/fonts/emoji-icon-font-mit-license.txt create mode 100644 nevmes-gui/crates/epaint/fonts/emoji-icon-font.ttf create mode 100644 nevmes-gui/crates/epaint/fonts/list_fonts.py create mode 100644 nevmes-gui/crates/epaint/src/bezier.rs create mode 100644 nevmes-gui/crates/epaint/src/image.rs create mode 100644 nevmes-gui/crates/epaint/src/lib.rs create mode 100644 nevmes-gui/crates/epaint/src/mesh.rs create mode 100644 nevmes-gui/crates/epaint/src/mutex.rs create mode 100644 nevmes-gui/crates/epaint/src/shadow.rs create mode 100644 nevmes-gui/crates/epaint/src/shape.rs create mode 100644 nevmes-gui/crates/epaint/src/shape_transform.rs create mode 100644 nevmes-gui/crates/epaint/src/stats.rs create mode 100644 nevmes-gui/crates/epaint/src/stroke.rs create mode 100644 nevmes-gui/crates/epaint/src/tessellator.rs create mode 100644 nevmes-gui/crates/epaint/src/text/cursor.rs create mode 100644 nevmes-gui/crates/epaint/src/text/font.rs create mode 100644 nevmes-gui/crates/epaint/src/text/fonts.rs create mode 100644 nevmes-gui/crates/epaint/src/text/mod.rs create mode 100644 nevmes-gui/crates/epaint/src/text/text_layout.rs create mode 100644 nevmes-gui/crates/epaint/src/text/text_layout_types.rs create mode 100644 nevmes-gui/crates/epaint/src/texture_atlas.rs create mode 100644 nevmes-gui/crates/epaint/src/texture_handle.rs create mode 100644 nevmes-gui/crates/epaint/src/textures.rs create mode 100644 nevmes-gui/crates/epaint/src/util/mod.rs create mode 100644 nevmes-gui/crates/epaint/src/util/ordered_float.rs create mode 100644 nevmes-gui/src/apps/address_book.rs create mode 100644 nevmes-gui/src/apps/home.rs create mode 100644 nevmes-gui/src/apps/lock_screen.rs create mode 100644 nevmes-gui/src/apps/mailbox.rs create mode 100644 nevmes-gui/src/apps/mod.rs create mode 100644 nevmes-gui/src/apps/settings.rs create mode 100644 nevmes-gui/src/apps/wallet.rs create mode 100644 nevmes-gui/src/lib.rs create mode 100644 nevmes-gui/src/login.rs create mode 100644 nevmes-gui/src/main.rs create mode 100644 nevmes-gui/src/wrap_app.rs create mode 100644 nevmes-message/Cargo.lock create mode 100644 nevmes-message/Cargo.toml create mode 100644 nevmes-message/src/controller.rs create mode 100644 nevmes-message/src/lib.rs create mode 100644 nevmes-message/src/main.rs create mode 100755 scripts/build_all_and_run.sh create mode 100755 scripts/cleanall.sh create mode 100644 src/controller.rs create mode 100644 src/lib.rs create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..73ea578 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +*/target +/target +/core +/test-lmdb +/wallet +genkey-batch +monero-wallet-rpc.log +notes.txt +.vscode/settings.json diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..c510d16 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,4115 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ab_glyph" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe21446ad43aa56417a767f3e2f3d7c4ca522904de1dd640529a76e9c5c3b33c" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" + +[[package]] +name = "accesskit" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4803cf8c252f374ae6bfbb341e49e5a37f7601f2ce74a105927a663eba952c67" +dependencies = [ + "enumn", + "serde", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "adler32" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" + +[[package]] +name = "ahash" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if", + "once_cell", + "serde", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr", +] + +[[package]] +name = "android-activity" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c77a0045eda8b888c76ea473c2b0515ba6f471d318f8927c5c72240937035a6" +dependencies = [ + "android-properties", + "bitflags 1.3.2", + "cc", + "jni-sys", + "libc", + "log 0.4.17", + "ndk", + "ndk-context", + "ndk-sys", + "num_enum", +] + +[[package]] +name = "android-properties" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "342258dd14006105c2b75ab1bd7543a03bdf0cfc94383303ac212a04939dff6f" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-wincon", + "concolor-override", + "concolor-query", + "is-terminal", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23ea9e81bd02e310c216d080f6223c179012256e5151c41db88d12c88a1684d2" + +[[package]] +name = "anstyle-parse" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7d1bb534e9efed14f3e5f44e7dd1a4f709384023a4165199a4241e18dff0116" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-wincon" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3127af6145b149f3287bb9a0d10ad9c5692dba8c53ad48285e5bec4063834fa" +dependencies = [ + "anstyle", + "windows-sys 0.45.0", +] + +[[package]] +name = "arboard" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6041616acea41d67c4a984709ddab1587fd0b10efe5cc563fee954d2f011854" +dependencies = [ + "clipboard-win", + "log 0.4.17", + "objc", + "objc-foundation", + "objc_id", + "once_cell", + "parking_lot", + "thiserror", + "winapi", + "x11rb", +] + +[[package]] +name = "arrayref" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" + +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + +[[package]] +name = "async-stream" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad445822218ce64be7a341abfb0b1ea43b5c23aa83902542a4542e78309d8e5e" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4655ae1a7b0cdf149156f780c5bf3f1352bc53cbd9e0a361a7ef7b22947e965" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "async-trait" +version = "0.1.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.11", +] + +[[package]] +name = "atomic" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b88d82667eca772c4aa12f0f1348b3ae643424c8876448f3f7bd5787032e234c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "atomic_refcell" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "857253367827bd9d0fd973f0ef15506a96e79e41b0ad7aa691203a4e3214f6c8" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" + +[[package]] +name = "binascii" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72" + +[[package]] +name = "bitflags" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487f1e0fcbe47deb8b0574e646def1c903389d95241dd1bbcc6ce4a715dfc0c1" + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-sys" +version = "0.1.0-beta.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa55741ee90902547802152aaf3f8e5248aab7e21468089560d4c8840561146" +dependencies = [ + "objc-sys", +] + +[[package]] +name = "block2" +version = "0.2.0-alpha.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dd9e63c1744f755c2f60332b88de39d341e5e86239014ad839bd71c106dec42" +dependencies = [ + "block-sys", + "objc2-encode", +] + +[[package]] +name = "build-rs" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b00b8763668c99f8d9101b8a0dd82106f58265464531a79b2cef0d9a30c17dd2" + +[[package]] +name = "bumpalo" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" + +[[package]] +name = "bytemuck" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdde5c9cd29ebd706ce1b35600920a33550e402fc998a2e53ad3b42c3c47a192" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.11", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "calloop" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a59225be45a478d772ce015d9743e49e92798ece9e34eda9a6aa2a6a7f40192" +dependencies = [ + "log 0.4.17", + "nix 0.25.1", + "slotmap", + "thiserror", + "vec_map", +] + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +dependencies = [ + "jobserver", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfg-expr" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a35b255461940a32985c627ce82900867c61db1659764d3675ea81963f72a4c6" +dependencies = [ + "smallvec", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "cgl" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" +dependencies = [ + "libc", +] + +[[package]] +name = "checked_int_cast" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17cc5e6b5ab06331c33589842070416baa137e8b0eb912b008cfd4a78ada7919" + +[[package]] +name = "chrono" +version = "0.4.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-integer", + "num-traits", + "time 0.1.45", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "clap" +version = "4.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046ae530c528f252094e4a77886ee1374437744b2bff1497aa898bbddbbb29b3" +dependencies = [ + "clap_builder", + "clap_derive", + "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "223163f58c9a40c3b0a43e1c4b50a9ce09f007ea2cb1ec258a687945b4b7929f" +dependencies = [ + "anstream", + "anstyle", + "bitflags 1.3.2", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.11", +] + +[[package]] +name = "clap_lex" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1" + +[[package]] +name = "clipboard-win" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362" +dependencies = [ + "error-code", + "str-buf", + "winapi", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "combine" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "concolor-override" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a855d4a1978dc52fb0536a04d384c2c0c1aa273597f08b77c8c4d3b2eec6037f" + +[[package]] +name = "concolor-query" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d11d52c3d7ca2e6d0040212be9e4dbbcd78b6447f535b6b561f449427944cf" +dependencies = [ + "windows-sys 0.45.0", +] + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "conv" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ff10625fd0ac447827aa30ea8b861fead473bb60aeb73af6c1c58caf0d1299" +dependencies = [ + "custom_derive", +] + +[[package]] +name = "cookie" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24" +dependencies = [ + "percent-encoding", + "time 0.3.20", + "version_check", +] + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "core-graphics" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "foreign-types", + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "280a9f2d8b3a38871a3c8a46fb80db65e5e5ed97da80c4d08bf27fb63e35e181" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset 0.8.0", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cstr-argument" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bd9c8e659a473bce955ae5c35b116af38af11a7acb0b480e01f3ed348aeb40" +dependencies = [ + "cfg-if", + "memchr", +] + +[[package]] +name = "custom_derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9" + +[[package]] +name = "cxx" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn 2.0.11", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.11", +] + +[[package]] +name = "deflate" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174" +dependencies = [ + "adler32", + "byteorder", +] + +[[package]] +name = "devise" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6eacefd3f541c66fc61433d65e54e0e46e0a029a819a7dbbc7a7b489e8a85f8" +dependencies = [ + "devise_codegen", + "devise_core", +] + +[[package]] +name = "devise_codegen" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8cf4b8dd484ede80fd5c547592c46c3745a617c8af278e2b72bea86b2dfed6" +dependencies = [ + "devise_core", + "quote", +] + +[[package]] +name = "devise_core" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35b50dba0afdca80b187392b24f2499a88c336d5a8493e4b4ccfb608708be56a" +dependencies = [ + "bitflags 2.0.2", + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn 2.0.11", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common", + "subtle", +] + +[[package]] +name = "digest_auth" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa30657988b2ced88f68fe490889e739bf98d342916c33ed3100af1d6f1cbc9c" +dependencies = [ + "digest 0.9.0", + "hex", + "md-5", + "rand", + "sha2 0.9.9", +] + +[[package]] +name = "diqwest" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5169a8000168e1ed2347ddd48be77530e1d976625008e89f9c3e4a35169d46" +dependencies = [ + "async-trait", + "digest_auth", + "reqwest", + "url", +] + +[[package]] +name = "directories-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dlib" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794" +dependencies = [ + "libloading", +] + +[[package]] +name = "downcast-rs" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" + +[[package]] +name = "ecolor" +version = "0.21.0" +dependencies = [ + "bytemuck", + "serde", +] + +[[package]] +name = "eframe" +version = "0.21.3" +dependencies = [ + "bytemuck", + "directories-next", + "egui", + "egui-winit", + "egui_glow", + "glow", + "glutin", + "glutin-winit", + "js-sys", + "percent-encoding", + "raw-window-handle", + "ron", + "serde", + "thiserror", + "tracing", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winit", +] + +[[package]] +name = "egui" +version = "0.21.0" +dependencies = [ + "accesskit", + "ahash", + "epaint", + "nohash-hasher", + "ron", + "serde", + "tracing", +] + +[[package]] +name = "egui-winit" +version = "0.21.1" +dependencies = [ + "android-activity", + "arboard", + "egui", + "instant", + "serde", + "smithay-clipboard", + "tracing", + "webbrowser", + "winit", +] + +[[package]] +name = "egui_extras" +version = "0.21.0" +dependencies = [ + "egui", + "image", + "serde", +] + +[[package]] +name = "egui_glow" +version = "0.21.0" +dependencies = [ + "bytemuck", + "egui", + "glow", + "memoffset 0.6.5", + "tracing", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + +[[package]] +name = "emath" +version = "0.21.0" +dependencies = [ + "bytemuck", + "serde", +] + +[[package]] +name = "encoding_rs" +version = "0.8.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "enumn" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48016319042fb7c87b78d2993084a831793a897a5cd1a2a67cab9d1eeb4b7d76" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.11", +] + +[[package]] +name = "env_logger" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +dependencies = [ + "humantime", + "is-terminal", + "log 0.4.17", + "regex", + "termcolor", +] + +[[package]] +name = "epaint" +version = "0.21.0" +dependencies = [ + "ab_glyph", + "ahash", + "atomic_refcell", + "bytemuck", + "ecolor", + "emath", + "nohash-hasher", + "parking_lot", + "serde", +] + +[[package]] +name = "errno" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d6a0976c999d473fe89ad888d5a284e55366d9dc9038b1ba2aa15128c4afa0" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.45.0", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "error-code" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21" +dependencies = [ + "libc", + "str-buf", +] + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "fdeflate" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d329bdeac514ee06249dabc27877490f17f5d371ec693360768b838e19f3ae10" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "figment" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e56602b469b2201400dec66a66aec5a9b8761ee97cd1b8c96ab2483fcc16cc9" +dependencies = [ + "atomic", + "pear", + "serde", + "toml 0.5.11", + "uncased", + "version_check", +] + +[[package]] +name = "flate2" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" +dependencies = [ + "crc32fast", + "miniz_oxide 0.6.2", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "531ac96c6ff5fd7c62263c5e3c67a603af4fcaee2e1a0ae5565ba3a11e69e549" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "164713a5a0dcc3e7b4b1ed7d3b433cabc18025386f9339346e8daf15963cf7ac" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86d7a0c1aa76363dac491de0ee99faf6941128376f1cf96f07db7603b7de69dd" + +[[package]] +name = "futures-io" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89d422fa3cbe3b40dca574ab087abb5bc98258ea57eea3fd6f1fa7162c778b91" + +[[package]] +name = "futures-sink" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec93083a4aecafb2a80a885c9de1f0ccae9dbd32c2bb54b0c3a65690e0b8d2f2" + +[[package]] +name = "futures-task" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd65540d33b37b16542a0438c12e6aeead10d4ac5d05bd3f805b8f35ab592879" + +[[package]] +name = "futures-util" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ef6b17e481503ec85211fed8f39d1970f128935ca1f814cd32ac4a6842e84ab" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "gcc" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" + +[[package]] +name = "generator" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33a20a288a94683f5f4da0adecdbe095c94a77c295e514cc6484e9394dd8376e" +dependencies = [ + "cc", + "libc", + "log 0.4.17", + "rustversion", + "windows 0.44.0", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "gethostname" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "gif" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3edd93c6756b4dfaf2709eafcc345ba2636565295c198a9cfbf75fa5e3e00b06" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log 0.4.17", + "xml-rs", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "glow" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e007a07a24de5ecae94160f141029e9a347282cfe25d1d58d85d845cf3130f1" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glutin" +version = "0.30.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f89bab9ec7715de13d5d5402238e66f48e3a5ae636ebb45aba4013c962e2ff15" +dependencies = [ + "bitflags 1.3.2", + "cfg_aliases", + "cgl", + "core-foundation", + "dispatch", + "glutin_egl_sys", + "glutin_glx_sys", + "glutin_wgl_sys", + "libloading", + "objc2", + "once_cell", + "raw-window-handle", + "wayland-sys 0.30.1", + "windows-sys 0.45.0", + "x11-dl", +] + +[[package]] +name = "glutin-winit" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "629a873fc04062830bfe8f97c03773bcd7b371e23bcc465d0a61448cd1588fa4" +dependencies = [ + "cfg_aliases", + "glutin", + "raw-window-handle", + "winit", +] + +[[package]] +name = "glutin_egl_sys" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5aaf0abb5c4148685b33101ae326a207946b4d3764d6cdc79f8316cdaa8367d" +dependencies = [ + "gl_generator", + "windows-sys 0.45.0", +] + +[[package]] +name = "glutin_glx_sys" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b53cb5fe568964aa066a3ba91eac5ecbac869fb0842cd0dc9e412434f1a1494" +dependencies = [ + "gl_generator", + "x11-dl", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef89398e90033fc6bc65e9bd42fd29bbbfd483bda5b56dc5562f455550618165" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "gpg-error" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89aaeddbfb92313378c58e98abadaaa34082b3855f1d455576eeeda08bd592c" +dependencies = [ + "libgpg-error-sys", +] + +[[package]] +name = "gpgme" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57539732fbe58eacdb984734b72b470ed0bca3ab7a49813271878567025ac44f" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "conv", + "cstr-argument", + "gpg-error", + "gpgme-sys", + "libc", + "memoffset 0.7.1", + "once_cell", + "smallvec", + "static_assertions", +] + +[[package]] +name = "gpgme-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "509223d659c06e4a26229437d6ac917723f02d31917c86c6ecd50e8369741cf7" +dependencies = [ + "build-rs", + "libc", + "libgpg-error-sys", + "system-deps", + "winreg", +] + +[[package]] +name = "h2" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be7b54589b581f624f566bf5d8eb2bab1db736c51528720b6bd36b96b55924d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.6", +] + +[[package]] +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "hyper" +version = "0.14.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc5e554ff619822309ffd57d8734d77cd5ce6238bc956f037ea06c58238c9899" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "716f12fbcfac6ffab0a5e9ec51d0a0ff70503742bb2dc7b99396394c9dc323f0" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows 0.47.0", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +dependencies = [ + "cxx", + "cxx-build", +] + +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "image" +version = "0.23.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "gif", + "jpeg-decoder", + "num-iter", + "num-rational", + "num-traits", + "png 0.16.8", + "scoped_threadpool", + "tiff", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown", + "serde", +] + +[[package]] +name = "inlinable_string" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09270fd4fa1111bc614ed2246c7ef56239a3063d5be0d1ec3b589c505d400aeb" +dependencies = [ + "hermit-abi 0.3.1", + "libc", + "windows-sys 0.45.0", +] + +[[package]] +name = "ipnet" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" + +[[package]] +name = "is-terminal" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "256017f749ab3117e93acb91063009e1f1bb56d03965b14c2c8df4eb02c524d8" +dependencies = [ + "hermit-abi 0.3.1", + "io-lifetimes", + "rustix", + "windows-sys 0.45.0", +] + +[[package]] +name = "itoa" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log 0.4.17", + "thiserror", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +dependencies = [ + "libc", +] + +[[package]] +name = "jpeg-decoder" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2" +dependencies = [ + "rayon", +] + +[[package]] +name = "js-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "jwt" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6204285f77fe7d9784db3fdc449ecce1a0114927a51d5a41c4c7a292011c015f" +dependencies = [ + "base64 0.13.1", + "crypto-common", + "digest 0.10.6", + "hmac", + "serde", + "serde_json", + "sha2 0.10.6", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + +[[package]] +name = "lazy_static" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" + +[[package]] +name = "libgpg-error-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c97079310f39c835d3bd73578379d040f779614bb331c7ffbb6630fee6420290" +dependencies = [ + "build-rs", + "system-deps", + "winreg", +] + +[[package]] +name = "liblmdb-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "feed38a3a580f60bf61aaa067b0ff4123395966839adeaf67258a9e50c4d2e49" +dependencies = [ + "gcc", + "libc", +] + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "link-cplusplus" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" +dependencies = [ + "cc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd550e73688e6d578f0ac2119e32b797a327631a42f9433e59d02e139c8df60d" + +[[package]] +name = "lmdb-rs" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aefe7b433f795629ce42f35ccf7a620c38bd457238bfaa2489dafc7e36167e7" +dependencies = [ + "bitflags 0.7.0", + "libc", + "liblmdb-sys", + "log 0.3.9", +] + +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" +dependencies = [ + "log 0.4.17", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "loom" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "serde", + "serde_json", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "md-5" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5a279bb9607f9f53c22d496eade00d138d1bdcccd07d74650387cf94942a15" +dependencies = [ + "block-buffer 0.9.0", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memmap2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memoffset" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" +dependencies = [ + "adler32", +] + +[[package]] +name = "miniz_oxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +dependencies = [ + "adler", + "autocfg", +] + +[[package]] +name = "miniz_oxide" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +dependencies = [ + "adler", +] + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +dependencies = [ + "libc", + "log 0.4.17", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.45.0", +] + +[[package]] +name = "multer" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed4198ce7a4cbd2a57af78d28c6fbb57d81ac5f1d6ad79ac6c5587419cbdf22" +dependencies = [ + "bytes", + "encoding_rs", + "futures-util", + "http", + "httparse", + "log 0.4.17", + "memchr", + "mime", + "spin", + "tokio", + "tokio-util", + "version_check", +] + +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static 1.4.0", + "libc", + "log 0.4.17", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "ndk" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "451422b7e4718271c8b5b3aadf5adedba43dc76312454b387e98fae0fc951aa0" +dependencies = [ + "bitflags 1.3.2", + "jni-sys", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.4.1+23.1.7779620" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cf2aae958bd232cac5069850591667ad422d263686d75b52a065f9badeee5a3" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "nevmes" +version = "0.1.0-alpha" +dependencies = [ + "env_logger", + "nevmes_auth", + "nevmes_contact", + "nevmes_core", + "nevmes_gui", + "nevmes_message", + "rocket", +] + +[[package]] +name = "nevmes_auth" +version = "0.1.0-alpha" +dependencies = [ + "env_logger", + "log 0.4.17", + "nevmes_core", + "rocket", +] + +[[package]] +name = "nevmes_contact" +version = "0.1.0-alpha" +dependencies = [ + "env_logger", + "log 0.4.17", + "nevmes_core", + "rocket", +] + +[[package]] +name = "nevmes_core" +version = "0.1.0-alpha" +dependencies = [ + "chrono", + "clap", + "diqwest", + "env_logger", + "gpgme", + "hex", + "hmac", + "jwt", + "lmdb-rs", + "log 0.4.17", + "rand", + "rand_core", + "reqwest", + "rocket", + "schedule_recv", + "serde", + "serde_json", + "sha2 0.10.6", + "tokio", +] + +[[package]] +name = "nevmes_gui" +version = "0.1.0-alpha" +dependencies = [ + "chrono", + "console_error_panic_hook", + "eframe", + "egui", + "egui_extras", + "hex", + "image", + "log 0.4.17", + "nevmes_core", + "qrcode", + "reqwest", + "serde", + "sha2 0.10.6", + "tokio", + "tracing", + "tracing-subscriber", + "tracing-wasm", + "wasm-bindgen-futures", +] + +[[package]] +name = "nevmes_message" +version = "0.1.0-alpha" +dependencies = [ + "env_logger", + "log 0.4.17", + "nevmes_core", + "rocket", +] + +[[package]] +name = "nix" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset 0.6.5", +] + +[[package]] +name = "nix" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset 0.6.5", +] + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +dependencies = [ + "hermit-abi 0.2.6", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + +[[package]] +name = "objc-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + +[[package]] +name = "objc-sys" +version = "0.2.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b9834c1e95694a05a828b59f55fa2afec6288359cda67146126b3f90a55d7" + +[[package]] +name = "objc2" +version = "0.3.0-beta.3.patch-leaks.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e01640f9f2cb1220bbe80325e179e532cb3379ebcd1bf2279d703c19fe3a468" +dependencies = [ + "block2", + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2-encode" +version = "2.0.0-pre.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abfcac41015b00a120608fdaa6938c44cb983fee294351cc4bac7638b4e50512" +dependencies = [ + "objc-sys", +] + +[[package]] +name = "objc_id" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] + +[[package]] +name = "once_cell" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "openssl" +version = "0.10.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "518915b97df115dd36109bfa429a48b8f737bd05508cf9588977b599648926d2" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "666416d899cf077260dac8698d60a60b435a46d57e82acb1be3d0dad87284e5b" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "orbclient" +version = "0.3.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e9829e16c5e112e94efb5e2ad1fe17f8c1c99bb0fcdc8c65c44e935d904767d" +dependencies = [ + "cfg-if", + "redox_syscall 0.2.16", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "owned_ttf_parser" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e25e9fb15717794fae58ab55c26e044103aad13186fbb625893f9a3bbcc24228" +dependencies = [ + "ttf-parser", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "windows-sys 0.45.0", +] + +[[package]] +name = "pear" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec95680a7087503575284e5063e14b694b7a9c0b065e5dceec661e0497127e8" +dependencies = [ + "inlinable_string", + "pear_codegen", + "yansi", +] + +[[package]] +name = "pear_codegen" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9661a3a53f93f09f2ea882018e4d7c88f6ff2956d809a276060476fd8c879d3c" +dependencies = [ + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn 2.0.11", +] + +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" + +[[package]] +name = "png" +version = "0.16.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "deflate", + "miniz_oxide 0.3.7", +] + +[[package]] +name = "png" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaeebc51f9e7d2c150d3f3bfeb667f2aa985db5ef1e3d212847bdedb488beeaa" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide 0.7.1", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e472a104799c74b514a57226160104aa483546de37e839ec50e3c2e41dd87534" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "606c4ba35817e2922a308af55ad51bab3645b59eae5c570d4a6cf07e36bd493b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.11", + "version_check", + "yansi", +] + +[[package]] +name = "qrcode" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d2f1455f3630c6e5107b4f2b94e74d76dea80736de0981fd27644216cff57f" +dependencies = [ + "checked_int_cast", + "image", +] + +[[package]] +name = "quote" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "raw-window-handle" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" + +[[package]] +name = "rayon" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "num_cpus", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall 0.2.16", + "thiserror", +] + +[[package]] +name = "ref-cast" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43faa91b1c8b36841ee70e97188a869d37ae21759da6846d4be66de5bf7b12c" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d2275aab483050ab2a7364c1a46604865ee7d6906684e08db0f090acf74f9e7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.11", +] + +[[package]] +name = "regex" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "reqwest" +version = "0.11.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b71749df584b7f4cac2c426c127a7c785a5106cc98f7a8feb044115f0fa254" +dependencies = [ + "base64 0.21.0", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log 0.4.17", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + +[[package]] +name = "rocket" +version = "0.5.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58734f7401ae5cfd129685b48f61182331745b357b96f2367f01aebaf1cc9cc9" +dependencies = [ + "async-stream", + "async-trait", + "atomic", + "binascii", + "bytes", + "either", + "figment", + "futures", + "indexmap", + "is-terminal", + "log 0.4.17", + "memchr", + "multer", + "num_cpus", + "parking_lot", + "pin-project-lite", + "rand", + "ref-cast", + "rocket_codegen", + "rocket_http", + "serde", + "serde_json", + "state", + "tempfile", + "time 0.3.20", + "tokio", + "tokio-stream", + "tokio-util", + "ubyte", + "version_check", + "yansi", +] + +[[package]] +name = "rocket_codegen" +version = "0.5.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7093353f14228c744982e409259fb54878ba9563d08214f2d880d59ff2fc508b" +dependencies = [ + "devise", + "glob", + "indexmap", + "proc-macro2", + "quote", + "rocket_http", + "syn 2.0.11", + "unicode-xid", +] + +[[package]] +name = "rocket_http" +version = "0.5.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936012c99162a03a67f37f9836d5f938f662e26f2717809761a9ac46432090f4" +dependencies = [ + "cookie", + "either", + "futures", + "http", + "hyper", + "indexmap", + "log 0.4.17", + "memchr", + "pear", + "percent-encoding", + "pin-project-lite", + "ref-cast", + "serde", + "smallvec", + "stable-pattern", + "state", + "time 0.3.20", + "tokio", + "uncased", +] + +[[package]] +name = "ron" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300a51053b1cb55c80b7a9fde4120726ddf25ca241a1cbb926626f62fb136bff" +dependencies = [ + "base64 0.13.1", + "bitflags 1.3.2", + "serde", +] + +[[package]] +name = "rustix" +version = "0.37.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e78cc525325c06b4a7ff02db283472f3c042b7ff0c391f96c6d5ac6f4f91b75" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.45.0", +] + +[[package]] +name = "rustversion" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" + +[[package]] +name = "ryu" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +dependencies = [ + "windows-sys 0.42.0", +] + +[[package]] +name = "schedule_recv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca1520cf9d3182329ceb57b9a6b37eb68fe94f5d46c0be4aa2d2a522ec3d40eb" +dependencies = [ + "lazy_static 0.2.11", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scoped_threadpool" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "scratch" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" + +[[package]] +name = "sctk-adwaita" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda4e97be1fd174ccc2aae81c8b694e803fa99b34e8fd0f057a9d70698e3ed09" +dependencies = [ + "ab_glyph", + "log 0.4.17", + "memmap2", + "smithay-client-toolkit", + "tiny-skia", +] + +[[package]] +name = "security-framework" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.159" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.159" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.11", +] + +[[package]] +name = "serde_json" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d721eca97ac802aa7777b701877c8004d950fc142651367300d21c1cc0194744" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.6", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static 1.4.0", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "238abfbb77c1915110ad968465608b68e869e0772622c9656714e73e5a1a522f" + +[[package]] +name = "slab" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg", +] + +[[package]] +name = "slotmap" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "smithay-client-toolkit" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f307c47d32d2715eb2e0ece5589057820e0e5e70d07c247d1063e844e107f454" +dependencies = [ + "bitflags 1.3.2", + "calloop", + "dlib", + "lazy_static 1.4.0", + "log 0.4.17", + "memmap2", + "nix 0.24.3", + "pkg-config", + "wayland-client", + "wayland-cursor", + "wayland-protocols", +] + +[[package]] +name = "smithay-clipboard" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a345c870a1fae0b1b779085e81b51e614767c239e93503588e54c5b17f4b0e8" +dependencies = [ + "smithay-client-toolkit", + "wayland-client", +] + +[[package]] +name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "spin" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0959fd6f767df20b231736396e4f602171e00d95205676286e79d4a4eb67bef" + +[[package]] +name = "stable-pattern" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4564168c00635f88eaed410d5efa8131afa8d8699a612c80c455a0ba05c21045" +dependencies = [ + "memchr", +] + +[[package]] +name = "state" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbe866e1e51e8260c9eed836a042a5e7f6726bb2b411dffeaa712e19c388f23b" +dependencies = [ + "loom", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "str-buf" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0" + +[[package]] +name = "strict-num" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9df65f20698aeed245efdde3628a6b559ea1239bbb871af1b6e3b58c413b2bd1" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21e3787bb71465627110e7d87ed4faaa36c1f61042ee67badb9e2ef173accc40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "system-deps" +version = "6.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555fc8147af6256f3931a36bb83ad0023240ce9cf2b319dec8236fd1f220b05f" +dependencies = [ + "cfg-expr", + "heck", + "pkg-config", + "toml 0.7.3", + "version-compare", +] + +[[package]] +name = "tempfile" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall 0.3.5", + "rustix", + "windows-sys 0.45.0", +] + +[[package]] +name = "termcolor" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.11", +] + +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tiff" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a53f4706d65497df0c4349241deddf35f84cee19c87ed86ea8ca590f4464437" +dependencies = [ + "jpeg-decoder", + "miniz_oxide 0.4.4", + "weezl", +] + +[[package]] +name = "time" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + +[[package]] +name = "time" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" +dependencies = [ + "itoa", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" + +[[package]] +name = "time-macros" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" +dependencies = [ + "time-core", +] + +[[package]] +name = "tiny-skia" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfef3412c6975196fdfac41ef232f910be2bb37b9dd3313a49a1a6bc815a5bdb" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "png 0.17.8", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4b5edac058fc98f51c935daea4d805b695b38e2f151241cad125ade2a2ac20d" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001" +dependencies = [ + "autocfg", + "bytes", + "libc", + "mio", + "num_cpus", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.45.0", +] + +[[package]] +name = "tokio-macros" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.11", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fb52b74f05dbf495a8fba459fdc331812b96aa086d9eb78101fa0d4569c3313" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "toml" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b403acf6f2bb0859c93c7f0d967cb4a75a7ac552100f9322faf64dc047669b21" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static 1.4.0", + "log 0.4.17", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "tracing-wasm" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4575c663a174420fa2d78f4108ff68f65bf2fbb7dd89f33749b6e826b3626e07" +dependencies = [ + "tracing", + "tracing-subscriber", + "wasm-bindgen", +] + +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + +[[package]] +name = "ttf-parser" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0609f771ad9c6155384897e1df4d948e692667cc0588548b68eb44d052b27633" + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "ubyte" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c81f0dae7d286ad0d9366d7679a77934cfc3cf3a8d67e82669794412b2368fe6" +dependencies = [ + "serde", +] + +[[package]] +name = "uncased" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b01702b0fd0b3fadcf98e098780badda8742d4f4a7676615cad90e8ac73622" +dependencies = [ + "serde", + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + +[[package]] +name = "unicode-ident" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "url" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version-compare" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "walkdir" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log 0.4.17", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +dependencies = [ + "bumpalo", + "log 0.4.17", + "once_cell", + "proc-macro2", + "quote", + "syn 1.0.109", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" + +[[package]] +name = "wayland-client" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f3b068c05a039c9f755f881dc50f01732214f5685e379829759088967c46715" +dependencies = [ + "bitflags 1.3.2", + "downcast-rs", + "libc", + "nix 0.24.3", + "scoped-tls", + "wayland-commons", + "wayland-scanner", + "wayland-sys 0.29.5", +] + +[[package]] +name = "wayland-commons" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8691f134d584a33a6606d9d717b95c4fa20065605f798a3f350d78dced02a902" +dependencies = [ + "nix 0.24.3", + "once_cell", + "smallvec", + "wayland-sys 0.29.5", +] + +[[package]] +name = "wayland-cursor" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661" +dependencies = [ + "nix 0.24.3", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b950621f9354b322ee817a23474e479b34be96c2e909c14f7bc0100e9a970bc6" +dependencies = [ + "bitflags 1.3.2", + "wayland-client", + "wayland-commons", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f4303d8fa22ab852f789e75a967f0a2cdc430a607751c0499bada3e451cbd53" +dependencies = [ + "proc-macro2", + "quote", + "xml-rs", +] + +[[package]] +name = "wayland-sys" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be12ce1a3c39ec7dba25594b97b42cb3195d54953ddb9d3d95a7c3902bc6e9d4" +dependencies = [ + "dlib", + "lazy_static 1.4.0", + "pkg-config", +] + +[[package]] +name = "wayland-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b2a02ac608e07132978689a6f9bf4214949c85998c247abadd4f4129b1aa06" +dependencies = [ + "dlib", + "lazy_static 1.4.0", + "log 0.4.17", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webbrowser" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b692165700260bbd40fbc5ff23766c03e339fbaca907aeea5cb77bf0a553ca83" +dependencies = [ + "core-foundation", + "dirs", + "jni", + "log 0.4.17", + "ndk-context", + "objc", + "raw-window-handle", + "url", + "web-sys", +] + +[[package]] +name = "weezl" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-wsapoll" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c17110f57155602a80dca10be03852116403c9ff3cd25b079d666f2aa3df6e" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2649ff315bee4c98757f15dac226efe3d81927adbb6e882084bb1ee3e0c330a7" +dependencies = [ + "windows-targets 0.47.0", +] + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f8996d3f43b4b2d44327cd71b7b0efd1284ab60e6e9d0e8b630e18555d87d3e" +dependencies = [ + "windows_aarch64_gnullvm 0.47.0", + "windows_aarch64_msvc 0.47.0", + "windows_i686_gnu 0.47.0", + "windows_i686_msvc 0.47.0", + "windows_x86_64_gnu 0.47.0", + "windows_x86_64_gnullvm 0.47.0", + "windows_x86_64_msvc 0.47.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831d567d53d4f3cb1db332b68e6e2b6260228eb4d99a777d8b2e8ed794027c90" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a42d54a417c60ce4f0e31661eed628f0fa5aca73448c093ec4d45fab4c51cdf" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1925beafdbb22201a53a483db861a5644123157c1c3cee83323a2ed565d71e3" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a8ef8f2f1711b223947d9b69b596cf5a4e452c930fb58b6fc3fdae7d0ec6b31" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7acaa0c2cf0d2ef99b61c308a0c3dbae430a51b7345dedec470bd8f53f5a3642" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a0628f71be1d11e17ca4a0e9e15b3a5180f6fbf1c2d55e3ba3f850378052c1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d6e62c256dc6d40b8c8707df17df8d774e60e39db723675241e7c15e910bce7" + +[[package]] +name = "winit" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f504e8c117b9015f618774f8d58cd4781f5a479bc41079c064f974cbb253874" +dependencies = [ + "android-activity", + "bitflags 1.3.2", + "cfg_aliases", + "core-foundation", + "core-graphics", + "dispatch", + "instant", + "libc", + "log 0.4.17", + "mio", + "ndk", + "objc2", + "once_cell", + "orbclient", + "percent-encoding", + "raw-window-handle", + "redox_syscall 0.3.5", + "sctk-adwaita", + "smithay-client-toolkit", + "wasm-bindgen", + "wayland-client", + "wayland-commons", + "wayland-protocols", + "wayland-scanner", + "web-sys", + "windows-sys 0.45.0", + "x11-dl", +] + +[[package]] +name = "winnow" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "x11rb" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "592b4883219f345e712b3209c62654ebda0bb50887f330cbd018d0f654bfd507" +dependencies = [ + "gethostname", + "nix 0.24.3", + "winapi", + "winapi-wsapoll", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56b245751c0ac9db0e006dc812031482784e434630205a93c73cfefcaabeac67" +dependencies = [ + "nix 0.24.3", +] + +[[package]] +name = "xcursor" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "463705a63313cd4301184381c5e8042f0a7e9b4bb63653f216311d4ae74690b7" +dependencies = [ + "nom", +] + +[[package]] +name = "xml-rs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" + +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..c0dd939 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "nevmes" +version = "0.1.0-alpha" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +env_logger = "0.10.0" +nevmes_auth = { path = "./nevmes-auth" } +nevmes_contact = { path = "./nevmes-contact" } +nevmes_core = { path = "./nevmes-core" } +nevmes_gui = { path = "./nevmes-gui" } +nevmes_message = { path = "./nevmes-message" } +rocket = { version = "0.5.0-rc.2", features = ["json"] } diff --git a/README.md b/README.md new file mode 100644 index 0000000..989e5d0 --- /dev/null +++ b/README.md @@ -0,0 +1,59 @@ +# NEVMES + +NEVidebla-MESago (invisible message) + +### gpg and i2p made simple for end-to-end encrypted, secure comms + +## About + +* send messages over the invisible internet +* vanity base32 addresses (advanced) +* automated mandatory gpg key encryption +* xmr payment integration + +## Dev + +* stack - rust (egui, rocket), lmdb, i2p-zero, monero(rpc, daemon), gpg +* install dependencies + * ubuntu example: `sudo apt update -y && sudo apt upgrade -y` + * `sudo apt install -y libssl-dev build-essential libgpgme-dev` +* download and run i2prouter start (optional: setup to run on boot similar tor daemon) +* `git clone https://github/com/creating2morrow/nevmes` +* `cd nevmes && ./scripts/build_all_and_run.sh "-- -h"` +* gui built with rust [egui](https://docs.rs/egui/latest/egui/) + +## API + +* remote/programmatic access +* secured by wallet signing +* jwt and jwp +* see [curl.md](./docs/curl.md) + +## Binaries + +* nevmes-auth - `internal` auth server +* nevmes-contact - `internal` add contacts server +* nevmes-core - application core logic +* nevmes-gui - primary user interface +* nevmes-message - `internal` message tx/read etc. server +* nevmes - `external` primary server for contact share, payment, message rx etc. +* [monerod](https://www.getmonero.org/downloads/#cli) - (not included) monero-wallet-rpc needs this + * can be overriden with remote node + * use the `--remote-node` flag +* [monero-wallet-rpc](https://www.getmonero.org/downloads/#cli) - (not included) interface for xmr wallet ops +* [i2p-zero](https://github.com/i2p-zero/i2p-zero/releases/tag/v1.20) - (not included) tunnel creation +* [i2p](https://geti2p.net/en/download) - http proxy (not included, *i2p-zero http proxy not working) + +## Manual + +[the manual](./docs/man.md) + +## Known issues + +* gui password and screen lock needs fixing up +* timeout out JWP payment approval screen with infinite loading +* message retry login (untested) +* test framework (in progress) +* docs on all `fn` and `structs` +* i2pd installer on home screen? +* and more daemon info and wallet functionality (multisig) diff --git a/docs/curl.md b/docs/curl.md new file mode 100644 index 0000000..12961b5 --- /dev/null +++ b/docs/curl.md @@ -0,0 +1,67 @@ +# Remote access + +NOTE: JWT for micro servers disabled in dev + +## Login API + +* send with dummy string on first request +* sign data in the response +* send second request with signature to get AUTHID and UID + +```bash +curl -iv -x localhost:9043/alice.b32.i2p/login/// +``` + +## generate invoice + +```bash +curl -iv http://localhost:9000/invoice +``` + +## get contact info + +```bash +curl -iv http://localhost:9000/share +``` + +## generate jwp + +```bash +curl -iv -X POST http://localhost:9000/prove -d '{"address": "", "confirmations":0,"hash":"", "message":"", "signature": ""}' -H 'Content-Type: application/json' +``` + +## health check + +```bash +curl -iv http://localhost:9000/xmr/version -H 'proof: eyJhbGciOiJIUzUxMiJ9...' +``` + +## add contact + +```bash +curl -iv -X POST http://localhost:9044/contact -d '{"cid": "KEEP EMPTY", "gpg_key": [1,2,3...], "i2p_address": "", "xmr_address": ""}' -H 'Content-Type: application/json' +``` + +## view contacts + +```bash +curl -iv http://localhost:9044/contacts +``` + +## send message + +```bash +curl -ivk localhost:9045/tx -d '{"uid":"123", "mid": "", "body": [1,2,3 ], "from": "alice.b32.i2p", "created": 0, "to": "bob.b32.i2p"}' -H 'Content-Type: application/json' +``` + +## receive message + +```bash +curl -ivk localhost:9000/message/rx -d '{"uid":"", "mid": "", "body": [1,2,3 ], "from": "alice.b32.i2p", "created": 0, "to": "bob.b32.i2p"}' -H 'Content-Type: application/json' -H 'proof: eyJhbGciOiJIUzUxMiJ9...' +``` + +## view messages + +```bash +curl -iv http://localhost:9044/messages +``` diff --git a/docs/man.md b/docs/man.md new file mode 100644 index 0000000..d1a158c --- /dev/null +++ b/docs/man.md @@ -0,0 +1,54 @@ +# The Manual + +## Architecture + +* gui +* three internal mircoservers (auth, contact and message) +* core code module and lmdb +* one external i2p hidden service +* jwt for internal auth, jwp for external + +### JWP (JSON Web Proof) + +* utilizes some external blockchain (nevmes uses monero) for authorization of auth tokens +* 32 byte random signing keys generated on app start-up +* `Hmac` internal, `Hmac` external (jwp) +* see [proof.rs](./nevmes-core/src/proof.rs) +`eyJhbGciOiJIUzUxMiJ9.eyJhZGRyZXNzIjoiNThvaUJMQUtBQ3JaeTRqVnRYdUFXMzlCOW1zR3dlbVVkSm9HVlozcGdSY1RoWHZqWjZ0RERqRGpuOE1mTUZ5cEtZMlU1U1B6SkE3NnFHeHhDdjJzd1Y0NjhFYkI2dEsiLCJoYXNoIjoiNzRhOTM5NTU1Y2EyMWJmY2MxYzlhMjhlYjFkN2M5MWZiMjRhYzRiOTY4MDk2Yzg4ODU1ODA3ODcwMDA1NmQ2NiIsIm1lc3NhZ2UiOiIiLCJzaWduYXR1cmUiOiJPdXRQcm9vZlYyWHdYTEJYV0VtbXlWd3YyOHFQRWQ0Mk14bm1FNTU3aUFEVHFGNjZDWG9LQ1ZFeFBqTVU4NFNIeWprZmdLd01WZEI4OUZkTkJ5QUxyeU1ZamVxQlY1U0VtU0V4MUJWWE1ITVJNWHVuMzh5aWVtcWhCcmVSWUdpRGdMN1lmRmVmemJSTnhlIn0.gH4RlLrxu3xqxNvsHv7lX1yYomg07yTlv6VEKpDfXwbDV4O267CXzm30G4YBQOfuDf3xpegUmeVXOScPvIZVRw` + +## Getting started + +* getting started the app will automatically generate an account and associated monero PRIMARY address. Only use it here to maintain privacy +* so first off, you need to someone to love, dont you want somebody to love... +* get your contact's i2p .b32.i2p address (top of gui screen) +* deposit some stagenet monero in your xmr account (address at top of gui screen) +* once unlocked nevmes xmr balance will display +* when authorizing to send to contact an invoice will be generated +* authorize payment and tx proof generation in the prompt +* this tx proof will be used to create an encrypted json web proof of payment with each contact +* think of it as a reusable, unforgeable coupon or ticket +* the invoice shows payment per blocks (time) +* default is 1 piconero per day +* the jwp is cached by the client until block time expiration at which time you will be required to authorize another payment + +## Adding a contact +* now that the bills are paid insert the .b32.i2p address on the contact line +* click add +* if all goes well you will have imported their public nevmes gpg app key +* dont reuse the app gpg keys anywhere else! +* don't forget to trust the contact with `sign key` in the `check status` window + +## Sending a message + +* the `check status` button will show current jwp for each contact +* `clear stale jwp` will purge data in case of timeout issues +* don't keep large amounts in nevmes just enoug for fees and jwps +* once a valid jwp is created (takes a few minutes) the `compose` button will be visible +* you need to click `check status` on contacts before sending to refresh jwp expiration check +* draft a plain text message, dont be shy +* enter recipient (.b32.i2p address) and press `send` +* plain text messages never leave your machine +* you can click `Refresh` button in the Mailbox to check for new messages +* messages must be decrypted by clicking `decrypt` + +## More to come... diff --git a/nevmes-auth/.gitignore b/nevmes-auth/.gitignore new file mode 100644 index 0000000..08d9607 --- /dev/null +++ b/nevmes-auth/.gitignore @@ -0,0 +1,6 @@ +/target +/core +/test-lmdb +/wallet +monero-wallet-rpc.log +notes.txt diff --git a/nevmes-auth/Cargo.lock b/nevmes-auth/Cargo.lock new file mode 100644 index 0000000..361694f --- /dev/null +++ b/nevmes-auth/Cargo.lock @@ -0,0 +1,2600 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "342258dd14006105c2b75ab1bd7543a03bdf0cfc94383303ac212a04939dff6f" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-wincon", + "concolor-override", + "concolor-query", + "is-terminal", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23ea9e81bd02e310c216d080f6223c179012256e5151c41db88d12c88a1684d2" + +[[package]] +name = "anstyle-parse" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7d1bb534e9efed14f3e5f44e7dd1a4f709384023a4165199a4241e18dff0116" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-wincon" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3127af6145b149f3287bb9a0d10ad9c5692dba8c53ad48285e5bec4063834fa" +dependencies = [ + "anstyle", + "windows-sys 0.45.0", +] + +[[package]] +name = "async-stream" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad445822218ce64be7a341abfb0b1ea43b5c23aa83902542a4542e78309d8e5e" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4655ae1a7b0cdf149156f780c5bf3f1352bc53cbd9e0a361a7ef7b22947e965" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "async-trait" +version = "0.1.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.11", +] + +[[package]] +name = "atomic" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b88d82667eca772c4aa12f0f1348b3ae643424c8876448f3f7bd5787032e234c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" + +[[package]] +name = "binascii" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72" + +[[package]] +name = "bitflags" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487f1e0fcbe47deb8b0574e646def1c903389d95241dd1bbcc6ce4a715dfc0c1" + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "build-rs" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b00b8763668c99f8d9101b8a0dd82106f58265464531a79b2cef0d9a30c17dd2" + +[[package]] +name = "bumpalo" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[package]] +name = "cfg-expr" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a35b255461940a32985c627ce82900867c61db1659764d3675ea81963f72a4c6" +dependencies = [ + "smallvec", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-integer", + "num-traits", + "time 0.1.45", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "clap" +version = "4.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046ae530c528f252094e4a77886ee1374437744b2bff1497aa898bbddbbb29b3" +dependencies = [ + "clap_builder", + "clap_derive", + "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "223163f58c9a40c3b0a43e1c4b50a9ce09f007ea2cb1ec258a687945b4b7929f" +dependencies = [ + "anstream", + "anstyle", + "bitflags 1.3.2", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.11", +] + +[[package]] +name = "clap_lex" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1" + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "concolor-override" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a855d4a1978dc52fb0536a04d384c2c0c1aa273597f08b77c8c4d3b2eec6037f" + +[[package]] +name = "concolor-query" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d11d52c3d7ca2e6d0040212be9e4dbbcd78b6447f535b6b561f449427944cf" +dependencies = [ + "windows-sys 0.45.0", +] + +[[package]] +name = "conv" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ff10625fd0ac447827aa30ea8b861fead473bb60aeb73af6c1c58caf0d1299" +dependencies = [ + "custom_derive", +] + +[[package]] +name = "cookie" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24" +dependencies = [ + "percent-encoding", + "time 0.3.20", + "version_check", +] + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "cpufeatures" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "280a9f2d8b3a38871a3c8a46fb80db65e5e5ed97da80c4d08bf27fb63e35e181" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cstr-argument" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bd9c8e659a473bce955ae5c35b116af38af11a7acb0b480e01f3ed348aeb40" +dependencies = [ + "cfg-if", + "memchr", +] + +[[package]] +name = "custom_derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9" + +[[package]] +name = "cxx" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn 2.0.11", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.11", +] + +[[package]] +name = "devise" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6eacefd3f541c66fc61433d65e54e0e46e0a029a819a7dbbc7a7b489e8a85f8" +dependencies = [ + "devise_codegen", + "devise_core", +] + +[[package]] +name = "devise_codegen" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8cf4b8dd484ede80fd5c547592c46c3745a617c8af278e2b72bea86b2dfed6" +dependencies = [ + "devise_core", + "quote", +] + +[[package]] +name = "devise_core" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35b50dba0afdca80b187392b24f2499a88c336d5a8493e4b4ccfb608708be56a" +dependencies = [ + "bitflags 2.0.2", + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn 2.0.11", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common", + "subtle", +] + +[[package]] +name = "digest_auth" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa30657988b2ced88f68fe490889e739bf98d342916c33ed3100af1d6f1cbc9c" +dependencies = [ + "digest 0.9.0", + "hex", + "md-5", + "rand", + "sha2 0.9.9", +] + +[[package]] +name = "diqwest" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5169a8000168e1ed2347ddd48be77530e1d976625008e89f9c3e4a35169d46" +dependencies = [ + "async-trait", + "digest_auth", + "reqwest", + "url", +] + +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + +[[package]] +name = "encoding_rs" +version = "0.8.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "env_logger" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +dependencies = [ + "humantime", + "is-terminal", + "log 0.4.17", + "regex", + "termcolor", +] + +[[package]] +name = "errno" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d6a0976c999d473fe89ad888d5a284e55366d9dc9038b1ba2aa15128c4afa0" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.45.0", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "figment" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e56602b469b2201400dec66a66aec5a9b8761ee97cd1b8c96ab2483fcc16cc9" +dependencies = [ + "atomic", + "pear", + "serde", + "toml 0.5.11", + "uncased", + "version_check", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "531ac96c6ff5fd7c62263c5e3c67a603af4fcaee2e1a0ae5565ba3a11e69e549" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "164713a5a0dcc3e7b4b1ed7d3b433cabc18025386f9339346e8daf15963cf7ac" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86d7a0c1aa76363dac491de0ee99faf6941128376f1cf96f07db7603b7de69dd" + +[[package]] +name = "futures-io" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89d422fa3cbe3b40dca574ab087abb5bc98258ea57eea3fd6f1fa7162c778b91" + +[[package]] +name = "futures-sink" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec93083a4aecafb2a80a885c9de1f0ccae9dbd32c2bb54b0c3a65690e0b8d2f2" + +[[package]] +name = "futures-task" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd65540d33b37b16542a0438c12e6aeead10d4ac5d05bd3f805b8f35ab592879" + +[[package]] +name = "futures-util" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ef6b17e481503ec85211fed8f39d1970f128935ca1f814cd32ac4a6842e84ab" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "gcc" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" + +[[package]] +name = "generator" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33a20a288a94683f5f4da0adecdbe095c94a77c295e514cc6484e9394dd8376e" +dependencies = [ + "cc", + "libc", + "log 0.4.17", + "rustversion", + "windows 0.44.0", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "gpg-error" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89aaeddbfb92313378c58e98abadaaa34082b3855f1d455576eeeda08bd592c" +dependencies = [ + "libgpg-error-sys", +] + +[[package]] +name = "gpgme" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57539732fbe58eacdb984734b72b470ed0bca3ab7a49813271878567025ac44f" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "conv", + "cstr-argument", + "gpg-error", + "gpgme-sys", + "libc", + "memoffset", + "once_cell", + "smallvec", + "static_assertions", +] + +[[package]] +name = "gpgme-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "509223d659c06e4a26229437d6ac917723f02d31917c86c6ecd50e8369741cf7" +dependencies = [ + "build-rs", + "libc", + "libgpg-error-sys", + "system-deps", + "winreg", +] + +[[package]] +name = "h2" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be7b54589b581f624f566bf5d8eb2bab1db736c51528720b6bd36b96b55924d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.6", +] + +[[package]] +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "hyper" +version = "0.14.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc5e554ff619822309ffd57d8734d77cd5ce6238bc956f037ea06c58238c9899" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "nevmes_auth" +version = "0.1.0-alpha" +dependencies = [ + "env_logger", + "nevmes_core", + "log 0.4.17", + "rocket", +] + +[[package]] +name = "nevmes_core" +version = "0.1.0-alpha" +dependencies = [ + "chrono", + "clap", + "diqwest", + "env_logger", + "gpgme", + "hex", + "hmac", + "jwt", + "lmdb-rs", + "log 0.4.17", + "rand", + "rand_core", + "reqwest", + "rocket", + "schedule_recv", + "serde", + "serde_json", + "sha2 0.10.6", + "tokio", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "716f12fbcfac6ffab0a5e9ec51d0a0ff70503742bb2dc7b99396394c9dc323f0" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows 0.47.0", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +dependencies = [ + "cxx", + "cxx-build", +] + +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown", + "serde", +] + +[[package]] +name = "inlinable_string" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09270fd4fa1111bc614ed2246c7ef56239a3063d5be0d1ec3b589c505d400aeb" +dependencies = [ + "hermit-abi 0.3.1", + "libc", + "windows-sys 0.45.0", +] + +[[package]] +name = "ipnet" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" + +[[package]] +name = "is-terminal" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "256017f749ab3117e93acb91063009e1f1bb56d03965b14c2c8df4eb02c524d8" +dependencies = [ + "hermit-abi 0.3.1", + "io-lifetimes", + "rustix", + "windows-sys 0.45.0", +] + +[[package]] +name = "itoa" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" + +[[package]] +name = "js-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "jwt" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6204285f77fe7d9784db3fdc449ecce1a0114927a51d5a41c4c7a292011c015f" +dependencies = [ + "base64 0.13.1", + "crypto-common", + "digest 0.10.6", + "hmac", + "serde", + "serde_json", + "sha2 0.10.6", +] + +[[package]] +name = "lazy_static" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" + +[[package]] +name = "libgpg-error-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c97079310f39c835d3bd73578379d040f779614bb331c7ffbb6630fee6420290" +dependencies = [ + "build-rs", + "system-deps", + "winreg", +] + +[[package]] +name = "liblmdb-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "feed38a3a580f60bf61aaa067b0ff4123395966839adeaf67258a9e50c4d2e49" +dependencies = [ + "gcc", + "libc", +] + +[[package]] +name = "link-cplusplus" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" +dependencies = [ + "cc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd550e73688e6d578f0ac2119e32b797a327631a42f9433e59d02e139c8df60d" + +[[package]] +name = "lmdb-rs" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aefe7b433f795629ce42f35ccf7a620c38bd457238bfaa2489dafc7e36167e7" +dependencies = [ + "bitflags 0.7.0", + "libc", + "liblmdb-sys", + "log 0.3.9", +] + +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" +dependencies = [ + "log 0.4.17", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "loom" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "serde", + "serde_json", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "md-5" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5a279bb9607f9f53c22d496eade00d138d1bdcccd07d74650387cf94942a15" +dependencies = [ + "block-buffer 0.9.0", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mio" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +dependencies = [ + "libc", + "log 0.4.17", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.45.0", +] + +[[package]] +name = "multer" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed4198ce7a4cbd2a57af78d28c6fbb57d81ac5f1d6ad79ac6c5587419cbdf22" +dependencies = [ + "bytes", + "encoding_rs", + "futures-util", + "http", + "httparse", + "log 0.4.17", + "memchr", + "mime", + "spin", + "tokio", + "tokio-util", + "version_check", +] + +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static 1.4.0", + "libc", + "log 0.4.17", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +dependencies = [ + "hermit-abi 0.2.6", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "openssl" +version = "0.10.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "518915b97df115dd36109bfa429a48b8f737bd05508cf9588977b599648926d2" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "666416d899cf077260dac8698d60a60b435a46d57e82acb1be3d0dad87284e5b" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "windows-sys 0.45.0", +] + +[[package]] +name = "pear" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec95680a7087503575284e5063e14b694b7a9c0b065e5dceec661e0497127e8" +dependencies = [ + "inlinable_string", + "pear_codegen", + "yansi", +] + +[[package]] +name = "pear_codegen" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9661a3a53f93f09f2ea882018e4d7c88f6ff2956d809a276060476fd8c879d3c" +dependencies = [ + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn 2.0.11", +] + +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e472a104799c74b514a57226160104aa483546de37e839ec50e3c2e41dd87534" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "606c4ba35817e2922a308af55ad51bab3645b59eae5c570d4a6cf07e36bd493b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.11", + "version_check", + "yansi", +] + +[[package]] +name = "quote" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "ref-cast" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43faa91b1c8b36841ee70e97188a869d37ae21759da6846d4be66de5bf7b12c" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d2275aab483050ab2a7364c1a46604865ee7d6906684e08db0f090acf74f9e7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.11", +] + +[[package]] +name = "regex" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "reqwest" +version = "0.11.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b71749df584b7f4cac2c426c127a7c785a5106cc98f7a8feb044115f0fa254" +dependencies = [ + "base64 0.21.0", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log 0.4.17", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + +[[package]] +name = "rocket" +version = "0.5.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58734f7401ae5cfd129685b48f61182331745b357b96f2367f01aebaf1cc9cc9" +dependencies = [ + "async-stream", + "async-trait", + "atomic", + "binascii", + "bytes", + "either", + "figment", + "futures", + "indexmap", + "is-terminal", + "log 0.4.17", + "memchr", + "multer", + "num_cpus", + "parking_lot", + "pin-project-lite", + "rand", + "ref-cast", + "rocket_codegen", + "rocket_http", + "serde", + "serde_json", + "state", + "tempfile", + "time 0.3.20", + "tokio", + "tokio-stream", + "tokio-util", + "ubyte", + "version_check", + "yansi", +] + +[[package]] +name = "rocket_codegen" +version = "0.5.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7093353f14228c744982e409259fb54878ba9563d08214f2d880d59ff2fc508b" +dependencies = [ + "devise", + "glob", + "indexmap", + "proc-macro2", + "quote", + "rocket_http", + "syn 2.0.11", + "unicode-xid", +] + +[[package]] +name = "rocket_http" +version = "0.5.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936012c99162a03a67f37f9836d5f938f662e26f2717809761a9ac46432090f4" +dependencies = [ + "cookie", + "either", + "futures", + "http", + "hyper", + "indexmap", + "log 0.4.17", + "memchr", + "pear", + "percent-encoding", + "pin-project-lite", + "ref-cast", + "serde", + "smallvec", + "stable-pattern", + "state", + "time 0.3.20", + "tokio", + "uncased", +] + +[[package]] +name = "rustix" +version = "0.37.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e78cc525325c06b4a7ff02db283472f3c042b7ff0c391f96c6d5ac6f4f91b75" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.45.0", +] + +[[package]] +name = "rustversion" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" + +[[package]] +name = "ryu" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" + +[[package]] +name = "schannel" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +dependencies = [ + "windows-sys 0.42.0", +] + +[[package]] +name = "schedule_recv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca1520cf9d3182329ceb57b9a6b37eb68fe94f5d46c0be4aa2d2a522ec3d40eb" +dependencies = [ + "lazy_static 0.2.11", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "scratch" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" + +[[package]] +name = "security-framework" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.159" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.159" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.11", +] + +[[package]] +name = "serde_json" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d721eca97ac802aa7777b701877c8004d950fc142651367300d21c1cc0194744" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.6", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static 1.4.0", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "spin" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0959fd6f767df20b231736396e4f602171e00d95205676286e79d4a4eb67bef" + +[[package]] +name = "stable-pattern" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4564168c00635f88eaed410d5efa8131afa8d8699a612c80c455a0ba05c21045" +dependencies = [ + "memchr", +] + +[[package]] +name = "state" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbe866e1e51e8260c9eed836a042a5e7f6726bb2b411dffeaa712e19c388f23b" +dependencies = [ + "loom", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21e3787bb71465627110e7d87ed4faaa36c1f61042ee67badb9e2ef173accc40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "system-deps" +version = "6.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555fc8147af6256f3931a36bb83ad0023240ce9cf2b319dec8236fd1f220b05f" +dependencies = [ + "cfg-expr", + "heck", + "pkg-config", + "toml 0.7.3", + "version-compare", +] + +[[package]] +name = "tempfile" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall 0.3.5", + "rustix", + "windows-sys 0.45.0", +] + +[[package]] +name = "termcolor" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "time" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + +[[package]] +name = "time" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" +dependencies = [ + "itoa", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" + +[[package]] +name = "time-macros" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" +dependencies = [ + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001" +dependencies = [ + "autocfg", + "bytes", + "libc", + "mio", + "num_cpus", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.45.0", +] + +[[package]] +name = "tokio-macros" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.11", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fb52b74f05dbf495a8fba459fdc331812b96aa086d9eb78101fa0d4569c3313" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "toml" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b403acf6f2bb0859c93c7f0d967cb4a75a7ac552100f9322faf64dc047669b21" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static 1.4.0", + "log 0.4.17", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "ubyte" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c81f0dae7d286ad0d9366d7679a77934cfc3cf3a8d67e82669794412b2368fe6" +dependencies = [ + "serde", +] + +[[package]] +name = "uncased" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b01702b0fd0b3fadcf98e098780badda8742d4f4a7676615cad90e8ac73622" +dependencies = [ + "serde", + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + +[[package]] +name = "unicode-ident" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "url" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version-compare" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log 0.4.17", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +dependencies = [ + "bumpalo", + "log 0.4.17", + "once_cell", + "proc-macro2", + "quote", + "syn 1.0.109", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" + +[[package]] +name = "web-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2649ff315bee4c98757f15dac226efe3d81927adbb6e882084bb1ee3e0c330a7" +dependencies = [ + "windows-targets 0.47.0", +] + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f8996d3f43b4b2d44327cd71b7b0efd1284ab60e6e9d0e8b630e18555d87d3e" +dependencies = [ + "windows_aarch64_gnullvm 0.47.0", + "windows_aarch64_msvc 0.47.0", + "windows_i686_gnu 0.47.0", + "windows_i686_msvc 0.47.0", + "windows_x86_64_gnu 0.47.0", + "windows_x86_64_gnullvm 0.47.0", + "windows_x86_64_msvc 0.47.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831d567d53d4f3cb1db332b68e6e2b6260228eb4d99a777d8b2e8ed794027c90" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a42d54a417c60ce4f0e31661eed628f0fa5aca73448c093ec4d45fab4c51cdf" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1925beafdbb22201a53a483db861a5644123157c1c3cee83323a2ed565d71e3" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a8ef8f2f1711b223947d9b69b596cf5a4e452c930fb58b6fc3fdae7d0ec6b31" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7acaa0c2cf0d2ef99b61c308a0c3dbae430a51b7345dedec470bd8f53f5a3642" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a0628f71be1d11e17ca4a0e9e15b3a5180f6fbf1c2d55e3ba3f850378052c1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d6e62c256dc6d40b8c8707df17df8d774e60e39db723675241e7c15e910bce7" + +[[package]] +name = "winnow" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" diff --git a/nevmes-auth/Cargo.toml b/nevmes-auth/Cargo.toml new file mode 100644 index 0000000..db17c02 --- /dev/null +++ b/nevmes-auth/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "nevmes_auth" +version = "0.1.0-alpha" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +env_logger = "0.10.0" +nevmes_core = { path = "../nevmes-core" } +log = "0.4" +rocket = { version = "0.5.0-rc.2", features = ["json"] } diff --git a/nevmes-auth/src/controller.rs b/nevmes-auth/src/controller.rs new file mode 100644 index 0000000..6555e87 --- /dev/null +++ b/nevmes-auth/src/controller.rs @@ -0,0 +1,17 @@ +use rocket::http::Status; +use rocket::response::status::Custom; +use rocket::serde::json::Json; +use rocket::get; + +use nevmes_core::{auth, models::*}; + +/// Login with wallet signature +/// +/// Creates user on initial login +/// +#[get("/login///")] +pub async fn login +(aid: String, uid: String,signature: String) -> Custom> { + let m_auth: Authorization = auth::verify_login(aid, uid, signature).await; + Custom(Status::Created, Json(m_auth)) +} diff --git a/nevmes-auth/src/lib.rs b/nevmes-auth/src/lib.rs new file mode 100644 index 0000000..dff684a --- /dev/null +++ b/nevmes-auth/src/lib.rs @@ -0,0 +1,2 @@ +// Keep complex logic in the core module +pub mod controller; diff --git a/nevmes-auth/src/main.rs b/nevmes-auth/src/main.rs new file mode 100644 index 0000000..2627daf --- /dev/null +++ b/nevmes-auth/src/main.rs @@ -0,0 +1,19 @@ +#[macro_use] +extern crate rocket; + +use nevmes_auth::*; +use nevmes_core::*; + +// The only changes in here should be mounting new controller methods + +#[launch] +async fn rocket() -> _ { + let config = rocket::Config { + port: utils::get_app_auth_port(), + ..rocket::Config::debug_default() + }; + env_logger::init(); + log::info!("nevmes-auth is online"); + rocket::custom(&config) + .mount("/", routes![controller::login]) +} diff --git a/nevmes-contact/.gitignore b/nevmes-contact/.gitignore new file mode 100644 index 0000000..08d9607 --- /dev/null +++ b/nevmes-contact/.gitignore @@ -0,0 +1,6 @@ +/target +/core +/test-lmdb +/wallet +monero-wallet-rpc.log +notes.txt diff --git a/nevmes-contact/Cargo.lock b/nevmes-contact/Cargo.lock new file mode 100644 index 0000000..a82786e --- /dev/null +++ b/nevmes-contact/Cargo.lock @@ -0,0 +1,2600 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "342258dd14006105c2b75ab1bd7543a03bdf0cfc94383303ac212a04939dff6f" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-wincon", + "concolor-override", + "concolor-query", + "is-terminal", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23ea9e81bd02e310c216d080f6223c179012256e5151c41db88d12c88a1684d2" + +[[package]] +name = "anstyle-parse" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7d1bb534e9efed14f3e5f44e7dd1a4f709384023a4165199a4241e18dff0116" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-wincon" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3127af6145b149f3287bb9a0d10ad9c5692dba8c53ad48285e5bec4063834fa" +dependencies = [ + "anstyle", + "windows-sys 0.45.0", +] + +[[package]] +name = "async-stream" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad445822218ce64be7a341abfb0b1ea43b5c23aa83902542a4542e78309d8e5e" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4655ae1a7b0cdf149156f780c5bf3f1352bc53cbd9e0a361a7ef7b22947e965" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "async-trait" +version = "0.1.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.12", +] + +[[package]] +name = "atomic" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b88d82667eca772c4aa12f0f1348b3ae643424c8876448f3f7bd5787032e234c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" + +[[package]] +name = "binascii" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72" + +[[package]] +name = "bitflags" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487f1e0fcbe47deb8b0574e646def1c903389d95241dd1bbcc6ce4a715dfc0c1" + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "build-rs" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b00b8763668c99f8d9101b8a0dd82106f58265464531a79b2cef0d9a30c17dd2" + +[[package]] +name = "bumpalo" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[package]] +name = "cfg-expr" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a35b255461940a32985c627ce82900867c61db1659764d3675ea81963f72a4c6" +dependencies = [ + "smallvec", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-integer", + "num-traits", + "time 0.1.45", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "clap" +version = "4.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046ae530c528f252094e4a77886ee1374437744b2bff1497aa898bbddbbb29b3" +dependencies = [ + "clap_builder", + "clap_derive", + "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "223163f58c9a40c3b0a43e1c4b50a9ce09f007ea2cb1ec258a687945b4b7929f" +dependencies = [ + "anstream", + "anstyle", + "bitflags 1.3.2", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.12", +] + +[[package]] +name = "clap_lex" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1" + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "concolor-override" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a855d4a1978dc52fb0536a04d384c2c0c1aa273597f08b77c8c4d3b2eec6037f" + +[[package]] +name = "concolor-query" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d11d52c3d7ca2e6d0040212be9e4dbbcd78b6447f535b6b561f449427944cf" +dependencies = [ + "windows-sys 0.45.0", +] + +[[package]] +name = "conv" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ff10625fd0ac447827aa30ea8b861fead473bb60aeb73af6c1c58caf0d1299" +dependencies = [ + "custom_derive", +] + +[[package]] +name = "cookie" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24" +dependencies = [ + "percent-encoding", + "time 0.3.20", + "version_check", +] + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "cpufeatures" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "280a9f2d8b3a38871a3c8a46fb80db65e5e5ed97da80c4d08bf27fb63e35e181" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cstr-argument" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bd9c8e659a473bce955ae5c35b116af38af11a7acb0b480e01f3ed348aeb40" +dependencies = [ + "cfg-if", + "memchr", +] + +[[package]] +name = "custom_derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9" + +[[package]] +name = "cxx" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn 2.0.12", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.12", +] + +[[package]] +name = "devise" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6eacefd3f541c66fc61433d65e54e0e46e0a029a819a7dbbc7a7b489e8a85f8" +dependencies = [ + "devise_codegen", + "devise_core", +] + +[[package]] +name = "devise_codegen" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8cf4b8dd484ede80fd5c547592c46c3745a617c8af278e2b72bea86b2dfed6" +dependencies = [ + "devise_core", + "quote", +] + +[[package]] +name = "devise_core" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35b50dba0afdca80b187392b24f2499a88c336d5a8493e4b4ccfb608708be56a" +dependencies = [ + "bitflags 2.0.2", + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn 2.0.12", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common", + "subtle", +] + +[[package]] +name = "digest_auth" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa30657988b2ced88f68fe490889e739bf98d342916c33ed3100af1d6f1cbc9c" +dependencies = [ + "digest 0.9.0", + "hex", + "md-5", + "rand", + "sha2 0.9.9", +] + +[[package]] +name = "diqwest" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5169a8000168e1ed2347ddd48be77530e1d976625008e89f9c3e4a35169d46" +dependencies = [ + "async-trait", + "digest_auth", + "reqwest", + "url", +] + +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + +[[package]] +name = "encoding_rs" +version = "0.8.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "env_logger" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +dependencies = [ + "humantime", + "is-terminal", + "log 0.4.17", + "regex", + "termcolor", +] + +[[package]] +name = "errno" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d6a0976c999d473fe89ad888d5a284e55366d9dc9038b1ba2aa15128c4afa0" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.45.0", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "figment" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e56602b469b2201400dec66a66aec5a9b8761ee97cd1b8c96ab2483fcc16cc9" +dependencies = [ + "atomic", + "pear", + "serde", + "toml 0.5.11", + "uncased", + "version_check", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-io" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-sink" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" + +[[package]] +name = "futures-task" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" + +[[package]] +name = "futures-util" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "gcc" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" + +[[package]] +name = "generator" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33a20a288a94683f5f4da0adecdbe095c94a77c295e514cc6484e9394dd8376e" +dependencies = [ + "cc", + "libc", + "log 0.4.17", + "rustversion", + "windows 0.44.0", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "gpg-error" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89aaeddbfb92313378c58e98abadaaa34082b3855f1d455576eeeda08bd592c" +dependencies = [ + "libgpg-error-sys", +] + +[[package]] +name = "gpgme" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57539732fbe58eacdb984734b72b470ed0bca3ab7a49813271878567025ac44f" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "conv", + "cstr-argument", + "gpg-error", + "gpgme-sys", + "libc", + "memoffset", + "once_cell", + "smallvec", + "static_assertions", +] + +[[package]] +name = "gpgme-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "509223d659c06e4a26229437d6ac917723f02d31917c86c6ecd50e8369741cf7" +dependencies = [ + "build-rs", + "libc", + "libgpg-error-sys", + "system-deps", + "winreg", +] + +[[package]] +name = "h2" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be7b54589b581f624f566bf5d8eb2bab1db736c51528720b6bd36b96b55924d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.6", +] + +[[package]] +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "hyper" +version = "0.14.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc5e554ff619822309ffd57d8734d77cd5ce6238bc956f037ea06c58238c9899" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "nevmes_contact" +version = "0.1.0-alpha" +dependencies = [ + "env_logger", + "nevmes_core", + "log 0.4.17", + "rocket", +] + +[[package]] +name = "nevmes_core" +version = "0.1.0-alpha" +dependencies = [ + "chrono", + "clap", + "diqwest", + "env_logger", + "gpgme", + "hex", + "hmac", + "jwt", + "lmdb-rs", + "log 0.4.17", + "rand", + "rand_core", + "reqwest", + "rocket", + "schedule_recv", + "serde", + "serde_json", + "sha2 0.10.6", + "tokio", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "716f12fbcfac6ffab0a5e9ec51d0a0ff70503742bb2dc7b99396394c9dc323f0" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows 0.47.0", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +dependencies = [ + "cxx", + "cxx-build", +] + +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown", + "serde", +] + +[[package]] +name = "inlinable_string" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09270fd4fa1111bc614ed2246c7ef56239a3063d5be0d1ec3b589c505d400aeb" +dependencies = [ + "hermit-abi 0.3.1", + "libc", + "windows-sys 0.45.0", +] + +[[package]] +name = "ipnet" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" + +[[package]] +name = "is-terminal" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "256017f749ab3117e93acb91063009e1f1bb56d03965b14c2c8df4eb02c524d8" +dependencies = [ + "hermit-abi 0.3.1", + "io-lifetimes", + "rustix", + "windows-sys 0.45.0", +] + +[[package]] +name = "itoa" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" + +[[package]] +name = "js-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "jwt" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6204285f77fe7d9784db3fdc449ecce1a0114927a51d5a41c4c7a292011c015f" +dependencies = [ + "base64 0.13.1", + "crypto-common", + "digest 0.10.6", + "hmac", + "serde", + "serde_json", + "sha2 0.10.6", +] + +[[package]] +name = "lazy_static" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" + +[[package]] +name = "libgpg-error-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c97079310f39c835d3bd73578379d040f779614bb331c7ffbb6630fee6420290" +dependencies = [ + "build-rs", + "system-deps", + "winreg", +] + +[[package]] +name = "liblmdb-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "feed38a3a580f60bf61aaa067b0ff4123395966839adeaf67258a9e50c4d2e49" +dependencies = [ + "gcc", + "libc", +] + +[[package]] +name = "link-cplusplus" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" +dependencies = [ + "cc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f" + +[[package]] +name = "lmdb-rs" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aefe7b433f795629ce42f35ccf7a620c38bd457238bfaa2489dafc7e36167e7" +dependencies = [ + "bitflags 0.7.0", + "libc", + "liblmdb-sys", + "log 0.3.9", +] + +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" +dependencies = [ + "log 0.4.17", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "loom" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "serde", + "serde_json", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "md-5" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5a279bb9607f9f53c22d496eade00d138d1bdcccd07d74650387cf94942a15" +dependencies = [ + "block-buffer 0.9.0", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mio" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +dependencies = [ + "libc", + "log 0.4.17", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.45.0", +] + +[[package]] +name = "multer" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed4198ce7a4cbd2a57af78d28c6fbb57d81ac5f1d6ad79ac6c5587419cbdf22" +dependencies = [ + "bytes", + "encoding_rs", + "futures-util", + "http", + "httparse", + "log 0.4.17", + "memchr", + "mime", + "spin", + "tokio", + "tokio-util", + "version_check", +] + +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static 1.4.0", + "libc", + "log 0.4.17", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +dependencies = [ + "hermit-abi 0.2.6", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "openssl" +version = "0.10.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "518915b97df115dd36109bfa429a48b8f737bd05508cf9588977b599648926d2" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "666416d899cf077260dac8698d60a60b435a46d57e82acb1be3d0dad87284e5b" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "windows-sys 0.45.0", +] + +[[package]] +name = "pear" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec95680a7087503575284e5063e14b694b7a9c0b065e5dceec661e0497127e8" +dependencies = [ + "inlinable_string", + "pear_codegen", + "yansi", +] + +[[package]] +name = "pear_codegen" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9661a3a53f93f09f2ea882018e4d7c88f6ff2956d809a276060476fd8c879d3c" +dependencies = [ + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn 2.0.12", +] + +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e472a104799c74b514a57226160104aa483546de37e839ec50e3c2e41dd87534" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "606c4ba35817e2922a308af55ad51bab3645b59eae5c570d4a6cf07e36bd493b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.12", + "version_check", + "yansi", +] + +[[package]] +name = "quote" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "ref-cast" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43faa91b1c8b36841ee70e97188a869d37ae21759da6846d4be66de5bf7b12c" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d2275aab483050ab2a7364c1a46604865ee7d6906684e08db0f090acf74f9e7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.12", +] + +[[package]] +name = "regex" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "reqwest" +version = "0.11.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b71749df584b7f4cac2c426c127a7c785a5106cc98f7a8feb044115f0fa254" +dependencies = [ + "base64 0.21.0", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log 0.4.17", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + +[[package]] +name = "rocket" +version = "0.5.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58734f7401ae5cfd129685b48f61182331745b357b96f2367f01aebaf1cc9cc9" +dependencies = [ + "async-stream", + "async-trait", + "atomic", + "binascii", + "bytes", + "either", + "figment", + "futures", + "indexmap", + "is-terminal", + "log 0.4.17", + "memchr", + "multer", + "num_cpus", + "parking_lot", + "pin-project-lite", + "rand", + "ref-cast", + "rocket_codegen", + "rocket_http", + "serde", + "serde_json", + "state", + "tempfile", + "time 0.3.20", + "tokio", + "tokio-stream", + "tokio-util", + "ubyte", + "version_check", + "yansi", +] + +[[package]] +name = "rocket_codegen" +version = "0.5.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7093353f14228c744982e409259fb54878ba9563d08214f2d880d59ff2fc508b" +dependencies = [ + "devise", + "glob", + "indexmap", + "proc-macro2", + "quote", + "rocket_http", + "syn 2.0.12", + "unicode-xid", +] + +[[package]] +name = "rocket_http" +version = "0.5.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936012c99162a03a67f37f9836d5f938f662e26f2717809761a9ac46432090f4" +dependencies = [ + "cookie", + "either", + "futures", + "http", + "hyper", + "indexmap", + "log 0.4.17", + "memchr", + "pear", + "percent-encoding", + "pin-project-lite", + "ref-cast", + "serde", + "smallvec", + "stable-pattern", + "state", + "time 0.3.20", + "tokio", + "uncased", +] + +[[package]] +name = "rustix" +version = "0.37.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e78cc525325c06b4a7ff02db283472f3c042b7ff0c391f96c6d5ac6f4f91b75" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.45.0", +] + +[[package]] +name = "rustversion" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" + +[[package]] +name = "ryu" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" + +[[package]] +name = "schannel" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +dependencies = [ + "windows-sys 0.42.0", +] + +[[package]] +name = "schedule_recv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca1520cf9d3182329ceb57b9a6b37eb68fe94f5d46c0be4aa2d2a522ec3d40eb" +dependencies = [ + "lazy_static 0.2.11", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "scratch" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" + +[[package]] +name = "security-framework" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.159" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.159" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.12", +] + +[[package]] +name = "serde_json" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d721eca97ac802aa7777b701877c8004d950fc142651367300d21c1cc0194744" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.6", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static 1.4.0", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "spin" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0959fd6f767df20b231736396e4f602171e00d95205676286e79d4a4eb67bef" + +[[package]] +name = "stable-pattern" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4564168c00635f88eaed410d5efa8131afa8d8699a612c80c455a0ba05c21045" +dependencies = [ + "memchr", +] + +[[package]] +name = "state" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbe866e1e51e8260c9eed836a042a5e7f6726bb2b411dffeaa712e19c388f23b" +dependencies = [ + "loom", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79d9531f94112cfc3e4c8f5f02cb2b58f72c97b7efd85f70203cc6d8efda5927" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "system-deps" +version = "6.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555fc8147af6256f3931a36bb83ad0023240ce9cf2b319dec8236fd1f220b05f" +dependencies = [ + "cfg-expr", + "heck", + "pkg-config", + "toml 0.7.3", + "version-compare", +] + +[[package]] +name = "tempfile" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall 0.3.5", + "rustix", + "windows-sys 0.45.0", +] + +[[package]] +name = "termcolor" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "time" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + +[[package]] +name = "time" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" +dependencies = [ + "itoa", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" + +[[package]] +name = "time-macros" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" +dependencies = [ + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001" +dependencies = [ + "autocfg", + "bytes", + "libc", + "mio", + "num_cpus", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.45.0", +] + +[[package]] +name = "tokio-macros" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.12", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fb52b74f05dbf495a8fba459fdc331812b96aa086d9eb78101fa0d4569c3313" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "toml" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b403acf6f2bb0859c93c7f0d967cb4a75a7ac552100f9322faf64dc047669b21" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static 1.4.0", + "log 0.4.17", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "ubyte" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c81f0dae7d286ad0d9366d7679a77934cfc3cf3a8d67e82669794412b2368fe6" +dependencies = [ + "serde", +] + +[[package]] +name = "uncased" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b01702b0fd0b3fadcf98e098780badda8742d4f4a7676615cad90e8ac73622" +dependencies = [ + "serde", + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + +[[package]] +name = "unicode-ident" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "url" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version-compare" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log 0.4.17", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +dependencies = [ + "bumpalo", + "log 0.4.17", + "once_cell", + "proc-macro2", + "quote", + "syn 1.0.109", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" + +[[package]] +name = "web-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2649ff315bee4c98757f15dac226efe3d81927adbb6e882084bb1ee3e0c330a7" +dependencies = [ + "windows-targets 0.47.0", +] + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f8996d3f43b4b2d44327cd71b7b0efd1284ab60e6e9d0e8b630e18555d87d3e" +dependencies = [ + "windows_aarch64_gnullvm 0.47.0", + "windows_aarch64_msvc 0.47.0", + "windows_i686_gnu 0.47.0", + "windows_i686_msvc 0.47.0", + "windows_x86_64_gnu 0.47.0", + "windows_x86_64_gnullvm 0.47.0", + "windows_x86_64_msvc 0.47.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831d567d53d4f3cb1db332b68e6e2b6260228eb4d99a777d8b2e8ed794027c90" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a42d54a417c60ce4f0e31661eed628f0fa5aca73448c093ec4d45fab4c51cdf" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1925beafdbb22201a53a483db861a5644123157c1c3cee83323a2ed565d71e3" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a8ef8f2f1711b223947d9b69b596cf5a4e452c930fb58b6fc3fdae7d0ec6b31" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7acaa0c2cf0d2ef99b61c308a0c3dbae430a51b7345dedec470bd8f53f5a3642" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a0628f71be1d11e17ca4a0e9e15b3a5180f6fbf1c2d55e3ba3f850378052c1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d6e62c256dc6d40b8c8707df17df8d774e60e39db723675241e7c15e910bce7" + +[[package]] +name = "winnow" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" diff --git a/nevmes-contact/Cargo.toml b/nevmes-contact/Cargo.toml new file mode 100644 index 0000000..bbe4b5c --- /dev/null +++ b/nevmes-contact/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "nevmes_contact" +version = "0.1.0-alpha" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +env_logger = "0.10.0" +nevmes_core = { path = "../nevmes-core" } +log = "0.4" +rocket = { version = "0.5.0-rc.2", features = ["json"] } diff --git a/nevmes-contact/src/controller.rs b/nevmes-contact/src/controller.rs new file mode 100644 index 0000000..e705570 --- /dev/null +++ b/nevmes-contact/src/controller.rs @@ -0,0 +1,33 @@ +use rocket::http::Status; +use rocket::response::status::Custom; +use rocket::serde::json::Json; +use rocket::{get, post}; + +use nevmes_core::{auth, contact, models::*, utils}; + +/// Add contact +#[post("/", data="")] +pub async fn add_contact +(req_contact: Json,_token: auth::BearerToken) -> Custom> { + let res_contact = contact::create(&req_contact).await; + if res_contact.cid == utils::empty_string() { + return Custom(Status::BadRequest, Json(Default::default())) + } + Custom(Status::Ok, Json(res_contact)) +} + +/// Return all contacts +#[get("/")] +pub async fn get_contacts +(_token: auth::BearerToken) -> Custom>> { + let contacts = contact::find_all(); + Custom(Status::Ok, Json(contacts)) +} + +/// trust contact +#[post("/")] +pub async fn trust_contact +(key: String, _token: auth::BearerToken) -> Status { + contact::trust_gpg(key); + Status::Ok +} diff --git a/nevmes-contact/src/lib.rs b/nevmes-contact/src/lib.rs new file mode 100644 index 0000000..dff684a --- /dev/null +++ b/nevmes-contact/src/lib.rs @@ -0,0 +1,2 @@ +// Keep complex logic in the core module +pub mod controller; diff --git a/nevmes-contact/src/main.rs b/nevmes-contact/src/main.rs new file mode 100644 index 0000000..23e4ea1 --- /dev/null +++ b/nevmes-contact/src/main.rs @@ -0,0 +1,21 @@ +#[macro_use] +extern crate rocket; + +use nevmes_contact::*; +use nevmes_core::*; + +// The only changes in here should be mounting new controller methods + +#[launch] +async fn rocket() -> _ { + let config = rocket::Config { + port: utils::get_app_contact_port(), + ..rocket::Config::debug_default() + }; + env_logger::init(); + log::info!("nevmes-contact is online"); + rocket::custom(&config) + .mount("/trust", routes![controller::trust_contact]) + .mount("/contact", routes![controller::add_contact]) + .mount("/contacts", routes![controller::get_contacts]) +} diff --git a/nevmes-core/Cargo.lock b/nevmes-core/Cargo.lock new file mode 100644 index 0000000..0a42092 --- /dev/null +++ b/nevmes-core/Cargo.lock @@ -0,0 +1,2590 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "342258dd14006105c2b75ab1bd7543a03bdf0cfc94383303ac212a04939dff6f" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-wincon", + "concolor-override", + "concolor-query", + "is-terminal", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23ea9e81bd02e310c216d080f6223c179012256e5151c41db88d12c88a1684d2" + +[[package]] +name = "anstyle-parse" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7d1bb534e9efed14f3e5f44e7dd1a4f709384023a4165199a4241e18dff0116" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-wincon" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3127af6145b149f3287bb9a0d10ad9c5692dba8c53ad48285e5bec4063834fa" +dependencies = [ + "anstyle", + "windows-sys 0.45.0", +] + +[[package]] +name = "async-stream" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad445822218ce64be7a341abfb0b1ea43b5c23aa83902542a4542e78309d8e5e" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4655ae1a7b0cdf149156f780c5bf3f1352bc53cbd9e0a361a7ef7b22947e965" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "async-trait" +version = "0.1.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.11", +] + +[[package]] +name = "atomic" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b88d82667eca772c4aa12f0f1348b3ae643424c8876448f3f7bd5787032e234c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" + +[[package]] +name = "binascii" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72" + +[[package]] +name = "bitflags" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487f1e0fcbe47deb8b0574e646def1c903389d95241dd1bbcc6ce4a715dfc0c1" + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "build-rs" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b00b8763668c99f8d9101b8a0dd82106f58265464531a79b2cef0d9a30c17dd2" + +[[package]] +name = "bumpalo" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[package]] +name = "cfg-expr" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a35b255461940a32985c627ce82900867c61db1659764d3675ea81963f72a4c6" +dependencies = [ + "smallvec", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-integer", + "num-traits", + "time 0.1.45", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "clap" +version = "4.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046ae530c528f252094e4a77886ee1374437744b2bff1497aa898bbddbbb29b3" +dependencies = [ + "clap_builder", + "clap_derive", + "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "223163f58c9a40c3b0a43e1c4b50a9ce09f007ea2cb1ec258a687945b4b7929f" +dependencies = [ + "anstream", + "anstyle", + "bitflags 1.3.2", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.11", +] + +[[package]] +name = "clap_lex" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1" + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "concolor-override" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a855d4a1978dc52fb0536a04d384c2c0c1aa273597f08b77c8c4d3b2eec6037f" + +[[package]] +name = "concolor-query" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d11d52c3d7ca2e6d0040212be9e4dbbcd78b6447f535b6b561f449427944cf" +dependencies = [ + "windows-sys 0.45.0", +] + +[[package]] +name = "conv" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ff10625fd0ac447827aa30ea8b861fead473bb60aeb73af6c1c58caf0d1299" +dependencies = [ + "custom_derive", +] + +[[package]] +name = "cookie" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24" +dependencies = [ + "percent-encoding", + "time 0.3.20", + "version_check", +] + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "cpufeatures" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "280a9f2d8b3a38871a3c8a46fb80db65e5e5ed97da80c4d08bf27fb63e35e181" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cstr-argument" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bd9c8e659a473bce955ae5c35b116af38af11a7acb0b480e01f3ed348aeb40" +dependencies = [ + "cfg-if", + "memchr", +] + +[[package]] +name = "custom_derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9" + +[[package]] +name = "cxx" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn 2.0.11", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.11", +] + +[[package]] +name = "devise" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6eacefd3f541c66fc61433d65e54e0e46e0a029a819a7dbbc7a7b489e8a85f8" +dependencies = [ + "devise_codegen", + "devise_core", +] + +[[package]] +name = "devise_codegen" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8cf4b8dd484ede80fd5c547592c46c3745a617c8af278e2b72bea86b2dfed6" +dependencies = [ + "devise_core", + "quote", +] + +[[package]] +name = "devise_core" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35b50dba0afdca80b187392b24f2499a88c336d5a8493e4b4ccfb608708be56a" +dependencies = [ + "bitflags 2.0.2", + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn 2.0.11", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common", + "subtle", +] + +[[package]] +name = "digest_auth" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa30657988b2ced88f68fe490889e739bf98d342916c33ed3100af1d6f1cbc9c" +dependencies = [ + "digest 0.9.0", + "hex", + "md-5", + "rand", + "sha2 0.9.9", +] + +[[package]] +name = "diqwest" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5169a8000168e1ed2347ddd48be77530e1d976625008e89f9c3e4a35169d46" +dependencies = [ + "async-trait", + "digest_auth", + "reqwest", + "url", +] + +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + +[[package]] +name = "encoding_rs" +version = "0.8.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "env_logger" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +dependencies = [ + "humantime", + "is-terminal", + "log 0.4.17", + "regex", + "termcolor", +] + +[[package]] +name = "errno" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d6a0976c999d473fe89ad888d5a284e55366d9dc9038b1ba2aa15128c4afa0" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.45.0", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "figment" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e56602b469b2201400dec66a66aec5a9b8761ee97cd1b8c96ab2483fcc16cc9" +dependencies = [ + "atomic", + "pear", + "serde", + "toml 0.5.11", + "uncased", + "version_check", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "531ac96c6ff5fd7c62263c5e3c67a603af4fcaee2e1a0ae5565ba3a11e69e549" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "164713a5a0dcc3e7b4b1ed7d3b433cabc18025386f9339346e8daf15963cf7ac" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86d7a0c1aa76363dac491de0ee99faf6941128376f1cf96f07db7603b7de69dd" + +[[package]] +name = "futures-io" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89d422fa3cbe3b40dca574ab087abb5bc98258ea57eea3fd6f1fa7162c778b91" + +[[package]] +name = "futures-sink" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec93083a4aecafb2a80a885c9de1f0ccae9dbd32c2bb54b0c3a65690e0b8d2f2" + +[[package]] +name = "futures-task" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd65540d33b37b16542a0438c12e6aeead10d4ac5d05bd3f805b8f35ab592879" + +[[package]] +name = "futures-util" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ef6b17e481503ec85211fed8f39d1970f128935ca1f814cd32ac4a6842e84ab" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "gcc" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" + +[[package]] +name = "generator" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33a20a288a94683f5f4da0adecdbe095c94a77c295e514cc6484e9394dd8376e" +dependencies = [ + "cc", + "libc", + "log 0.4.17", + "rustversion", + "windows 0.44.0", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "gpg-error" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89aaeddbfb92313378c58e98abadaaa34082b3855f1d455576eeeda08bd592c" +dependencies = [ + "libgpg-error-sys", +] + +[[package]] +name = "gpgme" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57539732fbe58eacdb984734b72b470ed0bca3ab7a49813271878567025ac44f" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "conv", + "cstr-argument", + "gpg-error", + "gpgme-sys", + "libc", + "memoffset", + "once_cell", + "smallvec", + "static_assertions", +] + +[[package]] +name = "gpgme-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "509223d659c06e4a26229437d6ac917723f02d31917c86c6ecd50e8369741cf7" +dependencies = [ + "build-rs", + "libc", + "libgpg-error-sys", + "system-deps", + "winreg", +] + +[[package]] +name = "h2" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be7b54589b581f624f566bf5d8eb2bab1db736c51528720b6bd36b96b55924d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.6", +] + +[[package]] +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "hyper" +version = "0.14.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc5e554ff619822309ffd57d8734d77cd5ce6238bc956f037ea06c58238c9899" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "nevmes_core" +version = "0.1.0-alpha" +dependencies = [ + "chrono", + "clap", + "diqwest", + "env_logger", + "gpgme", + "hex", + "hmac", + "jwt", + "lmdb-rs", + "log 0.4.17", + "rand", + "rand_core", + "reqwest", + "rocket", + "schedule_recv", + "serde", + "serde_json", + "sha2 0.10.6", + "tokio", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "716f12fbcfac6ffab0a5e9ec51d0a0ff70503742bb2dc7b99396394c9dc323f0" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows 0.47.0", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +dependencies = [ + "cxx", + "cxx-build", +] + +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown", + "serde", +] + +[[package]] +name = "inlinable_string" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09270fd4fa1111bc614ed2246c7ef56239a3063d5be0d1ec3b589c505d400aeb" +dependencies = [ + "hermit-abi 0.3.1", + "libc", + "windows-sys 0.45.0", +] + +[[package]] +name = "ipnet" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" + +[[package]] +name = "is-terminal" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "256017f749ab3117e93acb91063009e1f1bb56d03965b14c2c8df4eb02c524d8" +dependencies = [ + "hermit-abi 0.3.1", + "io-lifetimes", + "rustix", + "windows-sys 0.45.0", +] + +[[package]] +name = "itoa" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" + +[[package]] +name = "js-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "jwt" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6204285f77fe7d9784db3fdc449ecce1a0114927a51d5a41c4c7a292011c015f" +dependencies = [ + "base64 0.13.1", + "crypto-common", + "digest 0.10.6", + "hmac", + "serde", + "serde_json", + "sha2 0.10.6", +] + +[[package]] +name = "lazy_static" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" + +[[package]] +name = "libgpg-error-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c97079310f39c835d3bd73578379d040f779614bb331c7ffbb6630fee6420290" +dependencies = [ + "build-rs", + "system-deps", + "winreg", +] + +[[package]] +name = "liblmdb-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "feed38a3a580f60bf61aaa067b0ff4123395966839adeaf67258a9e50c4d2e49" +dependencies = [ + "gcc", + "libc", +] + +[[package]] +name = "link-cplusplus" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" +dependencies = [ + "cc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd550e73688e6d578f0ac2119e32b797a327631a42f9433e59d02e139c8df60d" + +[[package]] +name = "lmdb-rs" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aefe7b433f795629ce42f35ccf7a620c38bd457238bfaa2489dafc7e36167e7" +dependencies = [ + "bitflags 0.7.0", + "libc", + "liblmdb-sys", + "log 0.3.9", +] + +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" +dependencies = [ + "log 0.4.17", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "loom" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "serde", + "serde_json", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "md-5" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5a279bb9607f9f53c22d496eade00d138d1bdcccd07d74650387cf94942a15" +dependencies = [ + "block-buffer 0.9.0", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mio" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +dependencies = [ + "libc", + "log 0.4.17", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.45.0", +] + +[[package]] +name = "multer" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed4198ce7a4cbd2a57af78d28c6fbb57d81ac5f1d6ad79ac6c5587419cbdf22" +dependencies = [ + "bytes", + "encoding_rs", + "futures-util", + "http", + "httparse", + "log 0.4.17", + "memchr", + "mime", + "spin", + "tokio", + "tokio-util", + "version_check", +] + +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static 1.4.0", + "libc", + "log 0.4.17", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +dependencies = [ + "hermit-abi 0.2.6", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "openssl" +version = "0.10.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "518915b97df115dd36109bfa429a48b8f737bd05508cf9588977b599648926d2" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "666416d899cf077260dac8698d60a60b435a46d57e82acb1be3d0dad87284e5b" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "windows-sys 0.45.0", +] + +[[package]] +name = "pear" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec95680a7087503575284e5063e14b694b7a9c0b065e5dceec661e0497127e8" +dependencies = [ + "inlinable_string", + "pear_codegen", + "yansi", +] + +[[package]] +name = "pear_codegen" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9661a3a53f93f09f2ea882018e4d7c88f6ff2956d809a276060476fd8c879d3c" +dependencies = [ + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn 2.0.11", +] + +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e472a104799c74b514a57226160104aa483546de37e839ec50e3c2e41dd87534" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "606c4ba35817e2922a308af55ad51bab3645b59eae5c570d4a6cf07e36bd493b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.11", + "version_check", + "yansi", +] + +[[package]] +name = "quote" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "ref-cast" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43faa91b1c8b36841ee70e97188a869d37ae21759da6846d4be66de5bf7b12c" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d2275aab483050ab2a7364c1a46604865ee7d6906684e08db0f090acf74f9e7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.11", +] + +[[package]] +name = "regex" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "reqwest" +version = "0.11.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b71749df584b7f4cac2c426c127a7c785a5106cc98f7a8feb044115f0fa254" +dependencies = [ + "base64 0.21.0", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log 0.4.17", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + +[[package]] +name = "rocket" +version = "0.5.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58734f7401ae5cfd129685b48f61182331745b357b96f2367f01aebaf1cc9cc9" +dependencies = [ + "async-stream", + "async-trait", + "atomic", + "binascii", + "bytes", + "either", + "figment", + "futures", + "indexmap", + "is-terminal", + "log 0.4.17", + "memchr", + "multer", + "num_cpus", + "parking_lot", + "pin-project-lite", + "rand", + "ref-cast", + "rocket_codegen", + "rocket_http", + "serde", + "serde_json", + "state", + "tempfile", + "time 0.3.20", + "tokio", + "tokio-stream", + "tokio-util", + "ubyte", + "version_check", + "yansi", +] + +[[package]] +name = "rocket_codegen" +version = "0.5.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7093353f14228c744982e409259fb54878ba9563d08214f2d880d59ff2fc508b" +dependencies = [ + "devise", + "glob", + "indexmap", + "proc-macro2", + "quote", + "rocket_http", + "syn 2.0.11", + "unicode-xid", +] + +[[package]] +name = "rocket_http" +version = "0.5.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936012c99162a03a67f37f9836d5f938f662e26f2717809761a9ac46432090f4" +dependencies = [ + "cookie", + "either", + "futures", + "http", + "hyper", + "indexmap", + "log 0.4.17", + "memchr", + "pear", + "percent-encoding", + "pin-project-lite", + "ref-cast", + "serde", + "smallvec", + "stable-pattern", + "state", + "time 0.3.20", + "tokio", + "uncased", +] + +[[package]] +name = "rustix" +version = "0.37.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e78cc525325c06b4a7ff02db283472f3c042b7ff0c391f96c6d5ac6f4f91b75" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.45.0", +] + +[[package]] +name = "rustversion" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" + +[[package]] +name = "ryu" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" + +[[package]] +name = "schannel" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +dependencies = [ + "windows-sys 0.42.0", +] + +[[package]] +name = "schedule_recv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca1520cf9d3182329ceb57b9a6b37eb68fe94f5d46c0be4aa2d2a522ec3d40eb" +dependencies = [ + "lazy_static 0.2.11", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "scratch" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" + +[[package]] +name = "security-framework" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.159" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.159" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.11", +] + +[[package]] +name = "serde_json" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d721eca97ac802aa7777b701877c8004d950fc142651367300d21c1cc0194744" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.6", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static 1.4.0", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "spin" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0959fd6f767df20b231736396e4f602171e00d95205676286e79d4a4eb67bef" + +[[package]] +name = "stable-pattern" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4564168c00635f88eaed410d5efa8131afa8d8699a612c80c455a0ba05c21045" +dependencies = [ + "memchr", +] + +[[package]] +name = "state" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbe866e1e51e8260c9eed836a042a5e7f6726bb2b411dffeaa712e19c388f23b" +dependencies = [ + "loom", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21e3787bb71465627110e7d87ed4faaa36c1f61042ee67badb9e2ef173accc40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "system-deps" +version = "6.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555fc8147af6256f3931a36bb83ad0023240ce9cf2b319dec8236fd1f220b05f" +dependencies = [ + "cfg-expr", + "heck", + "pkg-config", + "toml 0.7.3", + "version-compare", +] + +[[package]] +name = "tempfile" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall 0.3.5", + "rustix", + "windows-sys 0.45.0", +] + +[[package]] +name = "termcolor" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "time" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + +[[package]] +name = "time" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" +dependencies = [ + "itoa", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" + +[[package]] +name = "time-macros" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" +dependencies = [ + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001" +dependencies = [ + "autocfg", + "bytes", + "libc", + "mio", + "num_cpus", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.45.0", +] + +[[package]] +name = "tokio-macros" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.11", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fb52b74f05dbf495a8fba459fdc331812b96aa086d9eb78101fa0d4569c3313" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "toml" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b403acf6f2bb0859c93c7f0d967cb4a75a7ac552100f9322faf64dc047669b21" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static 1.4.0", + "log 0.4.17", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "ubyte" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c81f0dae7d286ad0d9366d7679a77934cfc3cf3a8d67e82669794412b2368fe6" +dependencies = [ + "serde", +] + +[[package]] +name = "uncased" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b01702b0fd0b3fadcf98e098780badda8742d4f4a7676615cad90e8ac73622" +dependencies = [ + "serde", + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + +[[package]] +name = "unicode-ident" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "url" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version-compare" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log 0.4.17", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +dependencies = [ + "bumpalo", + "log 0.4.17", + "once_cell", + "proc-macro2", + "quote", + "syn 1.0.109", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" + +[[package]] +name = "web-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2649ff315bee4c98757f15dac226efe3d81927adbb6e882084bb1ee3e0c330a7" +dependencies = [ + "windows-targets 0.47.0", +] + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f8996d3f43b4b2d44327cd71b7b0efd1284ab60e6e9d0e8b630e18555d87d3e" +dependencies = [ + "windows_aarch64_gnullvm 0.47.0", + "windows_aarch64_msvc 0.47.0", + "windows_i686_gnu 0.47.0", + "windows_i686_msvc 0.47.0", + "windows_x86_64_gnu 0.47.0", + "windows_x86_64_gnullvm 0.47.0", + "windows_x86_64_msvc 0.47.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831d567d53d4f3cb1db332b68e6e2b6260228eb4d99a777d8b2e8ed794027c90" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a42d54a417c60ce4f0e31661eed628f0fa5aca73448c093ec4d45fab4c51cdf" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1925beafdbb22201a53a483db861a5644123157c1c3cee83323a2ed565d71e3" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a8ef8f2f1711b223947d9b69b596cf5a4e452c930fb58b6fc3fdae7d0ec6b31" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7acaa0c2cf0d2ef99b61c308a0c3dbae430a51b7345dedec470bd8f53f5a3642" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a0628f71be1d11e17ca4a0e9e15b3a5180f6fbf1c2d55e3ba3f850378052c1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d6e62c256dc6d40b8c8707df17df8d774e60e39db723675241e7c15e910bce7" + +[[package]] +name = "winnow" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" diff --git a/nevmes-core/Cargo.toml b/nevmes-core/Cargo.toml new file mode 100644 index 0000000..2ea7151 --- /dev/null +++ b/nevmes-core/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "nevmes_core" +version = "0.1.0-alpha" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +chrono = "0.4.23" +clap = { version = "4.1.4", features = ["derive"] } +diqwest = "1.1.1" +env_logger = "0.10.0" +gpgme = "0.11.0" +hex = "0.4.3" +hmac = "0.12.1" +jwt = "0.16.0" +lmdb-rs = "0.7.6" +log = "0.4" +rand = "0.8.5" +rand_core = "0.6.4" +reqwest = { version = "0.11.12", features = ["json"] } +rocket = { version = "0.5.0-rc.2", features = ["json"] } +schedule_recv = "0.1.0" +sha2 = "0.10.6" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0.94" +tokio = "1.25.0" + +[profile.release] +prod = true diff --git a/nevmes-core/src/args.rs b/nevmes-core/src/args.rs new file mode 100644 index 0000000..269e785 --- /dev/null +++ b/nevmes-core/src/args.rs @@ -0,0 +1,127 @@ +use clap::Parser; + +/// cmd line args +#[derive(Parser, Default, Debug)] +#[command(author, version, about, long_about = None)] +pub struct Args { + /// set release environment + #[arg( + short, + long, + help = "Set release environment (dev, prod)", + default_value = "dev" + )] + pub release_env: String, + /// Monero location + #[arg( + long, + help = "Monero download absolute path.", + default_value = "monero-x86_64-linux-gnu-v0.18.2.0" + )] + pub monero_location: String, + /// Monero RPC host + #[arg( + long, + help = "Monero RPC host.", + default_value = "http://localhost:38083" + )] + pub monero_rpc_host: String, + /// Monero blockchain location + #[arg( + long, + help = "Monero blockchain location", + default_value = "/home/user/.bitmonero" + )] + pub monero_blockchain_dir: String, + /// Absolute path to i2p zero + #[arg( + long, + help = "Absolute path to i2p-zero directroy", + default_value = "/home/user/i2p-zero-linux.v1.20" + )] + pub i2p_zero_dir: String, + /// Monero RPC daemon host + #[arg( + long, + help = "Monero RPC daemon.", + default_value = "http://localhost:38081" + )] + pub monero_rpc_daemon: String, + /// Monero RPC Username + #[arg(long, help = "Monero RPC username.", default_value = "user")] + pub monero_rpc_username: String, + /// Monero RPC credential + #[arg(long, help = "Monero RPC credential.", default_value = "pass")] + pub monero_rpc_cred: String, + /// Token expiration in minutes + #[arg( + short, + long, + help = "Set the token expiration limit in minutes.", + default_value = "60" + )] + pub token_timeout: i64, + /// Payment Threshold + #[arg( + short, + long, + help = "Set a payment threshold in piconeros", + default_value = "1" + )] + pub payment_threshold: u128, + /// Confirmation Threshold + #[arg( + short, + long, + help = "Set a confirmation expiration for payments", + default_value = "720" + )] + pub confirmation_threshold: u64, + /// Application port + #[arg(long, help = "Set app port", default_value = "9000")] + pub port: u16, + /// Auth port + #[arg(long, help = "Set app auth port", default_value = "9043")] + pub auth_port: u16, + /// Contact saving port + #[arg(long, help = "Set app contact saving port", default_value = "9044")] + pub contact_port: u16, + /// Messaging sending port + #[arg(long, help = "Set app message sending port", default_value = "9045")] + pub message_port: u16, + /// Auto trust contact gpg keys (DISABLED) + #[arg( + long, + help = "FUTURE FEATURE. Auto trust contacts. DISABLED", + default_value = "false" + )] + pub auto_trust: bool, + /// Start with gui + #[arg( + long, + help = "Start the graphical user interface", + default_value = "false" + )] + pub gui: bool, + /// i2p http proxy host + #[arg( + long, + help = "i2p http proxy host", + default_value = "http://localhost:4444" + )] + pub i2p_proxy_host: String, + /// Connect wallet rpc for a remote-node, WARNING: may harm privacy + #[arg( + long, + help = "connect to remote node, don't use locally running monerod", + default_value = "false" + )] + pub remote_node: bool, + /// Connect to micro servers + #[arg( + long, + help = "allow remote access to mirco server functionality", + default_value = "false" + )] + pub remote_access: bool, +} diff --git a/nevmes-core/src/auth.rs b/nevmes-core/src/auth.rs new file mode 100644 index 0000000..095ce04 --- /dev/null +++ b/nevmes-core/src/auth.rs @@ -0,0 +1,221 @@ +use crate::{args, models::*, db, monero, reqres, user, utils}; +use clap::Parser; +use log::{debug, error, info}; + +use rocket::http::Status; +use rocket::outcome::Outcome; +use rocket::request::FromRequest; +use rocket::{request, Request}; + +use hmac::{Hmac, Mac}; +use jwt::*; +use sha2::Sha384; +use std::collections::BTreeMap; + +/// Create authorization data to sign and expiration +pub fn create(address: &String) -> Authorization { + info!("creating auth"); + let aid: String = format!("auth{}", utils::generate_rnd()); + let rnd: String = utils::generate_rnd(); + let created: i64 = chrono::offset::Utc::now().timestamp(); + let token: String = create_token(String::from(address), created); + let new_auth = Authorization { + aid, + created, + uid: utils::empty_string(), + rnd, + token, + xmr_address: String::from(address), + }; + let s = db::Interface::open(); + debug!("insert auth: {:?}", &new_auth); + let k = &new_auth.aid; + db::Interface::write(&s.env, &s.handle, k, &Authorization::to_db(&new_auth)); + new_auth +} + +/// Authorization lookup for recurring requests +pub fn find(aid: &String) -> Authorization { + info!("searching for auth: {}", aid); + let s = db::Interface::open(); + let r = db::Interface::read(&s.env, &s.handle, &String::from(aid)); + debug!("auth read: {}", r); + if r == utils::empty_string() { + return Default::default(); + } + Authorization::from_db(String::from(aid), r) +} + +/// Update new authorization creation time +fn update_expiration(f_auth: Authorization, address: &String) -> Authorization { + info!("modify auth expiration"); + let data = utils::generate_rnd(); + let time: i64 = chrono::offset::Utc::now().timestamp(); + // update time, token and data to sign + let u_auth = Authorization::update_expiration( + f_auth, + time, + data, + create_token(String::from(address), time), + ); + let s = db::Interface::open(); + db::Interface::delete(&s.env, &s.handle, &u_auth.aid); + db::Interface::write( + &s.env, + &s.handle, + &u_auth.aid, + &Authorization::to_db(&u_auth), + ); + return u_auth; +} + +/// Performs the signature verfication against stored auth +pub async fn verify_login +(aid: String, uid: String, signature: String) -> Authorization { + let m_address: reqres::XmrRpcAddressResponse = monero::get_address().await; + let address = m_address.result.address; + let f_auth: Authorization = find(&aid); + if f_auth.xmr_address == utils::empty_string() { + error!("auth not found"); + return create(&address); + } + let data: String = String::from(&f_auth.rnd); + let sig_address: String = + monero::verify_signature(String::from(&address), data, String::from(&signature)).await; + if sig_address == utils::ApplicationErrors::LoginError.value() { + error!("signature validation failed"); + return f_auth; + } + let f_user: User = user::find(&uid); + if f_user.xmr_address == utils::empty_string() { + info!("creating new user"); + let u: User = user::create(&address); + // update auth with uid + let u_auth = Authorization::update_uid(f_auth, String::from(&u.uid)); + let s = db::Interface::open(); + db::Interface::delete(&s.env, &s.handle, &u_auth.aid); + db::Interface::write(&s.env, &s.handle, &u_auth.aid, &Authorization::to_db(&u_auth)); + return u_auth + } else if f_user.xmr_address != utils::empty_string() { + info!("returning user"); + let m_access = verify_access(&address, &signature).await; + if !m_access { return Default::default() } + return f_auth; + } else { + error!("error creating user"); + return Default::default() + } +} + +/// Called during auth flow to update data to sign and expiration +pub async fn verify_access(address: &String, signature: &String) -> bool { + // look up auth for address + let f_auth: Authorization = find(address); + if f_auth.xmr_address != utils::empty_string() { + // check expiration, generate new data to sign if necessary + let now: i64 = chrono::offset::Utc::now().timestamp(); + let expiration = get_auth_expiration(); + if now > f_auth.created + expiration { + update_expiration(f_auth, address); + return false; + } + } + // verify signature on the data if not expired + let data = f_auth.rnd; + let sig_address: String = + monero::verify_signature(String::from(address), data, String::from(signature)).await; + if sig_address == utils::ApplicationErrors::LoginError.value() { + debug!("signing failed"); + return false; + } + info!("auth verified"); + return true; +} + +/// get the auth expiration command line configuration +fn get_auth_expiration() -> i64 { + let args = args::Args::parse(); + args.token_timeout * 60 +} + +fn create_token(address: String, created: i64) -> String { + let jwt_secret_key = utils::get_jwt_secret_key(); + let key: Hmac = Hmac::new_from_slice(&jwt_secret_key.as_bytes()).expect("hash"); + let header = Header { + algorithm: AlgorithmType::Hs384, + ..Default::default() + }; + let mut claims = BTreeMap::new(); + let expiration = get_auth_expiration() * created; + claims.insert("address", address); + claims.insert("expiration", expiration.to_string()); + let token = Token::new(header, claims).sign_with_key(&key); + String::from(token.expect("expected token").as_str()) +} + +/// This token is used for internal micro server authentication +#[derive(Debug)] +pub struct BearerToken(String); + +#[derive(Debug)] +pub enum BearerTokenError { + Expired, + Missing, + Invalid, +} + +#[rocket::async_trait] +impl<'r> FromRequest<'r> for BearerToken { + type Error = BearerTokenError; + + async fn from_request(request: &'r Request<'_>) -> request::Outcome { + let env = utils::get_release_env(); + let dev = utils::ReleaseEnvironment::Development; + if env == dev { + return Outcome::Success(BearerToken(utils::empty_string())) + } + let token = request.headers().get_one("token"); + let m_address: reqres::XmrRpcAddressResponse = monero::get_address().await; + let address = m_address.result.address; + debug!("{}", address); + match token { + Some(token) => { + // check validity + let jwt_secret_key = utils::get_jwt_secret_key(); + let key: Hmac = Hmac::new_from_slice(&jwt_secret_key.as_bytes()).expect(""); + let jwt: Result< + Token, _>, + jwt::Error, + > = token.verify_with_key(&key); + return match jwt { + Ok(j) => { + let claims = j.claims(); + debug!("claim address: {}", claims["address"]); + // verify address + if claims["address"] != address { + return Outcome::Failure(( + Status::Unauthorized, + BearerTokenError::Invalid, + )); + } + // verify expiration + let now: i64 = chrono::offset::Utc::now().timestamp(); + let expire = match claims["expiration"].parse::() { + Ok(n) => n, + Err(_) => 0, + }; + if now > expire { + return Outcome::Failure(( + Status::Unauthorized, + BearerTokenError::Expired, + )); + } + Outcome::Success(BearerToken(String::from(token))) + } + Err(_) => Outcome::Failure((Status::Unauthorized, BearerTokenError::Invalid)), + }; + } + None => Outcome::Failure((Status::Unauthorized, BearerTokenError::Missing)), + } + } +} diff --git a/nevmes-core/src/contact.rs b/nevmes-core/src/contact.rs new file mode 100644 index 0000000..ec1c3d0 --- /dev/null +++ b/nevmes-core/src/contact.rs @@ -0,0 +1,154 @@ +// Contact repo/service layer +use crate::{db, gpg, models::*, utils, reqres, monero, i2p}; +use rocket::serde::json::Json; +use log::{debug, error, info}; +use std::error::Error; + +/// Create a new contact +pub async fn create(c: &Json) -> Contact { + let f_cid: String = format!("c{}", utils::generate_rnd()); + info!("creating contact: {}", f_cid); + let new_contact = Contact { + cid: String::from(&f_cid), + gpg_key: c.gpg_key.iter().cloned().collect(), + i2p_address: String::from(&c.i2p_address), + xmr_address: String::from(&c.xmr_address), + }; + let is_valid = validate_contact(c).await; + if !is_valid { + return Default::default(); + } + let import = c.gpg_key.iter().cloned().collect(); + gpg::import_key(String::from(&f_cid), import).unwrap(); + debug!("insert contact: {:?}", &new_contact); + let s = db::Interface::open(); + let k = &new_contact.cid; + db::Interface::write(&s.env, &s.handle, k, &Contact::to_db(&new_contact)); + // in order to retrieve all contact, write keys to with cl + let list_key = format!("cl"); + let r = db::Interface::read(&s.env, &s.handle, &String::from(&list_key)); + if r == utils::empty_string() { + debug!("creating contact index"); + } + let msg_list = [r, String::from(&f_cid)].join(","); + debug!("writing contact index {} for key {}", msg_list, list_key); + db::Interface::write(&s.env, &s.handle, &String::from(list_key), &msg_list); + new_contact +} + +/// Contact lookup +pub fn find(cid: &String) -> Contact { + let s = db::Interface::open(); + let r = db::Interface::read(&s.env, &s.handle, &String::from(cid)); + if r == utils::empty_string() { + error!("contact not found"); + return Default::default() + } + Contact::from_db(String::from(cid), r) +} + +/// All contact lookup +pub fn find_all() -> Vec { + let s = db::Interface::open(); + let list_key = format!("cl"); + let r = db::Interface::read(&s.env, &s.handle, &String::from(list_key)); + if r == utils::empty_string() { + error!("contact index not found"); + return Default::default() + } + let v_cid = r.split(","); + let v: Vec = v_cid.map(|s| String::from(s)).collect(); + let mut contacts: Vec = Vec::new(); + for id in v { + if id != utils::empty_string() { + let contact: Contact = find(&id); + contacts.push(contact); + } + } + contacts +} + +async fn validate_contact(j: &Json) -> bool { + info!("validating contact: {}", &j.cid); + let validate_address = monero::validate_address(&j.xmr_address).await; + j.cid.len() < utils::string_limit() + && j.i2p_address.len() < utils::string_limit() + && j.i2p_address.contains(".b32.i2p") + && j.gpg_key.len() < utils::gpg_key_limit() + && validate_address.result.valid +} + +/// Send our information +pub async fn share() -> Contact { + let m_address: reqres::XmrRpcAddressResponse = monero::get_address().await; + let gpg_key = gpg::export_key().unwrap_or(Vec::new()); + let i2p_address = i2p::get_destination(); + let xmr_address = m_address.result.address; + Contact { cid: utils::empty_string(),gpg_key,i2p_address,xmr_address } +} + +pub fn exists(from: &String) -> bool { + let all = find_all(); + let mut addresses: Vec = Vec::new(); + for c in all { addresses.push(c.i2p_address); } + return addresses.contains(from); +} + +/// Sign for trusted nevmes contacts +/// +/// UI/UX should have some prompt about the implication of trusting keys +/// +/// however that is beyond the scope of this app. nevmes assumes contacts +/// +/// using the app already have some level of knowledge about each other. +/// +/// Without signing the key message encryption and sending is not possible. +pub fn trust_gpg(key: String) { gpg::sign_key(&key).unwrap(); } + +/// Get invoice for jwp creation +pub async fn request_invoice(contact: String) -> Result> { + // TODO(c2m): Error handling for http 402 status + let host = utils::get_i2p_http_proxy(); + let proxy = reqwest::Proxy::http(&host)?; + let client = reqwest::Client::builder().proxy(proxy).build(); + match client?.get(format!("http://{}/invoice", contact)).send().await { + Ok(response) => { + let res = response.json::().await; + debug!("invoice request response: {:?}", res); + match res { + Ok(r) => { + Ok(r) + }, + _ => Ok(Default::default()), + } + } + Err(e) => { + error!("failed to generate invoice due to: {:?}", e); + Ok(Default::default()) + } + } +} + +/// Send the request to contact to add them +pub async fn add_contact_request(contact: String) -> Result> { + // TODO(c2m): Error handling for http 402 status + let host = utils::get_i2p_http_proxy(); + let proxy = reqwest::Proxy::http(&host)?; + let client = reqwest::Client::builder().proxy(proxy).build(); + match client?.get(format!("http://{}/share", contact)).send().await { + Ok(response) => { + let res = response.json::().await; + debug!("share response: {:?}", res); + match res { + Ok(r) => { + Ok(r) + }, + _ => Ok(Default::default()), + } + } + Err(e) => { + error!("failed to fetch contact info due to: {:?}", e); + Ok(Default::default()) + } + } +} diff --git a/nevmes-core/src/db.rs b/nevmes-core/src/db.rs new file mode 100644 index 0000000..3bdd457 --- /dev/null +++ b/nevmes-core/src/db.rs @@ -0,0 +1,61 @@ +// db created and exported from here +extern crate lmdb_rs as lmdb; + +use log::{debug, error}; +use lmdb::{EnvBuilder, DbFlags, Environment, DbHandle}; + +use crate::utils; + +pub struct Interface { + pub env: Environment, + pub handle: DbHandle, +} + +impl Interface { + pub fn open() -> Self { + let release_env = utils::get_release_env(); + let file_path = format!("/home/{}/.nevmes/", std::env::var("USER").unwrap_or(String::from("user"))); + let mut env_str: &str = "test-lmdb"; + if release_env != utils::ReleaseEnvironment::Development { env_str = "lmdb"; }; + let env = EnvBuilder::new().open(format!("{}/{}", file_path, env_str), 0o777).unwrap(); + let handle = env.get_default_db(DbFlags::empty()).unwrap(); + Interface { env, handle } + } + pub fn write(e: &Environment, h: &DbHandle, k: &str, v: &str) { + let txn = e.new_transaction().unwrap(); + { + // get a database bound to this transaction + let db = txn.bind(&h); + let pair = vec![(k,v)]; + for &(key, value) in pair.iter() { db.set(&key, &value).unwrap(); } + } + match txn.commit() { + Err(_) => error!("failed to commit!"), + Ok(_) => () + } + } + pub fn read(e: &Environment, h: &DbHandle, k: &str) -> String { + let reader = e.get_reader().unwrap(); + let db = reader.bind(&h); + let value = db.get::<&str>(&k).unwrap_or_else(|_| ""); + let r = String::from(value); + { + if r == utils::empty_string() { + debug!("Failed to read from db.") + } + } + r + } + pub fn delete(e: &Environment, h: &DbHandle, k: &str) { + let txn = e.new_transaction().unwrap(); + { + // get a database bound to this transaction + let db = txn.bind(&h); + db.del::<>(&k).unwrap_or_else(|_| error!("failed to delete")); + } + match txn.commit() { + Err(_) => error!("failed to commit!"), + Ok(_) => () + } + } +} diff --git a/nevmes-core/src/gpg.rs b/nevmes-core/src/gpg.rs new file mode 100644 index 0000000..93d3847 --- /dev/null +++ b/nevmes-core/src/gpg.rs @@ -0,0 +1,169 @@ +use log::{debug, error, info}; +use std::process::Command; +use gpgme::*; +use std::{error::Error, fs::File, io::Write}; +use crate::{utils, i2p}; + +/// Searches for key, returns empty string if none exists +/// +/// TODO(c2m): add more cli options +pub fn find_key() -> Result> { + info!("searching for application gpg key"); + let proto = Protocol::OpenPgp; + let mode = KeyListMode::LOCAL; + let mut ctx = Context::from_protocol(proto)?; + ctx.set_key_list_mode(mode)?; + let name = i2p::get_destination(); + let mut keys = ctx.find_keys([&name])?; + let mut k: String = utils::empty_string(); + for key in keys.by_ref().filter_map(|x| x.ok()) { + let r_key: &str = key.id().unwrap_or(""); + if String::from(r_key) != utils::empty_string() { + k = String::from(r_key); + break; + } else { + error!("error finding gpg key"); + } + } + if keys.finish()?.is_truncated() { + error!("key listing unexpectedly truncated"); + } + Ok(k) +} + +pub fn gen_key() { + info!("creating gpg key"); + let output = Command::new("gpg") + .args(["--batch", "--gen-key", "genkey-batch"]) + .spawn() + .expect("gpg key generation failed"); + debug!("{:?}", output.stdout); +} + +/// Export ascii armor app public gpg key +pub fn export_key() -> Result, Box> { + info!("exporting public key"); + let mut ctx = Context::from_protocol(Protocol::OpenPgp)?; + ctx.set_armor(true); + let name = i2p::get_destination(); + let keys = { + let mut key_iter = ctx.find_keys([&name])?; + let keys: Vec<_> = key_iter.by_ref().collect::>()?; + if key_iter.finish()?.is_truncated() { + Err("key listing unexpectedly truncated")?; + } + keys + }; + let mode = gpgme::ExportMode::empty(); + let mut output = Vec::new(); + ctx.export_keys(&keys, mode, &mut output) + .map_err(|e| format!("export failed: {:?}", e))?; + Ok(output) +} + +/// Import gpg keys from contacts +pub fn import_key(cid: String, key: Vec) -> Result<(), Box> { + info!("importing key: {}", hex::encode(&key)); + let filename = format!("{}.nevmes", &cid); + let mut f = File::create(&filename)?; + f.write_all(&key)?; + let mut ctx = Context::from_protocol(gpgme::Protocol::OpenPgp)?; + println!("reading file `{}'", &filename); + let input = File::open(&filename)?; + let mut data = Data::from_seekable_stream(input)?; + let mode = None; + mode.map(|m| data.set_encoding(m)); + ctx.import(&mut data).map_err(|e| format!("import failed {:?}", e))?; + utils::stage_cleanup(filename); + Ok(()) +} + +pub fn encrypt(name: String, body: &Vec) -> Result, Box> { + let proto = Protocol::OpenPgp; + let mut ctx = Context::from_protocol(proto)?; + ctx.set_armor(true); + let keys: Vec = ctx.find_keys([&name])? + .filter_map(|x| x.ok()) + .filter(|k| k.can_encrypt()) + .collect(); + let filename = format!("{}.nevmes", name); + let mut f = File::create(&filename)?; + f.write_all(body)?; + let mut input = File::open(&filename) + .map_err(|e| format!("can't open file `{}': {}", filename, e))?; + let mut output = Vec::new(); + ctx.encrypt(&keys, &mut input, &mut output) + .map_err(|e| format!("encrypting failed: {:?}", e))?; + debug!("encrypted message body: {}", String::from_utf8(output.iter().cloned().collect()).unwrap_or(utils::empty_string())); + utils::stage_cleanup(filename); + Ok(output) +} + +pub fn decrypt(mid: &String, body: &Vec) -> Result, Box> { + let proto = Protocol::OpenPgp; + let mut ctx = Context::from_protocol(proto)?; + ctx.set_armor(true); + let filename = format!("{}.nevmes", mid); + let mut f = File::create(&filename)?; + f.write_all(&body)?; + let mut input = File::open(&filename) + .map_err(|e| format!("can't open file `{}': {}", filename, e))?; + let mut output = Vec::new(); + ctx.decrypt(&mut input, &mut output) + .map_err(|e| format!("decrypting failed: {:?}", e))?; + utils::stage_cleanup(filename); + Ok(output) +} + +pub fn write_gen_batch() -> Result<(), Box> { + let name = i2p::get_destination(); + let data = format!( + "%no-protection + Key-Type: RSA + Key-Length: 4096 + Subkey-Type: ECC + Subkey-Curve: Curve25519 + Name-Real: {} + Name-Email: {} + Expire-Date: 0", name, name + ); + let filename = format!("genkey-batch"); + let mut f = File::create(&filename)?; + f.write_all(&data.into_bytes())?; + Ok(()) +} + +pub fn sign_key(key: &str) -> Result<(), Box> { + let mut ctx = Context::from_protocol(gpgme::Protocol::OpenPgp)?; + let mut keys = ctx.find_keys([key])?; + let mut k: String = utils::empty_string(); + for ak in keys.by_ref().filter_map(|x| x.ok()) { + let r_key: &str = ak.id().unwrap_or(""); + if String::from(r_key) != utils::empty_string() { + k = String::from(r_key); + break; + } else { + error!("error finding gpg key"); + } + } + debug!("key-id match: {}", k); + let mut k2s_ctx = Context::from_protocol(gpgme::Protocol::OpenPgp)?; + let key_to_sign = k2s_ctx + .get_key(k) + .map_err(|e| format!("no key matched given key-id: {:?}", e))?; + let name = Some(i2p::get_destination()); + if let Some(app_key) = name { + let key = k2s_ctx + .get_secret_key(app_key) + .map_err(|e| format!("unable to find signing key: {:?}", e))?; + debug!("app key: {:?}", key.id()); + k2s_ctx.add_signer(&key) + .map_err(|e| format!("add_signer() failed: {:?}", e))?; + } + + k2s_ctx.sign_key(&key_to_sign, None::, Default::default()) + .map_err(|e| format!("signing failed: {:?}", e))?; + + println!("Signed key for {}", key); + Ok(()) +} diff --git a/nevmes-core/src/i2p.rs b/nevmes-core/src/i2p.rs new file mode 100644 index 0000000..5a8b722 --- /dev/null +++ b/nevmes-core/src/i2p.rs @@ -0,0 +1,184 @@ +use std::{fs, env, process::Command}; +use log::{debug, error, info, warn}; +use reqwest::Client; +use serde::{Deserialize, Serialize}; +use crate::{args, utils}; +use clap::Parser; +use std::time::Duration; + +// TODO(c2m): debug i2p-zero http proxy + +#[derive(Debug)] +pub enum I2pStatus { + Accept, + Reject, +} + +impl I2pStatus { + pub fn value(&self) -> String { + match *self { + I2pStatus::Accept => String::from("Accepting tunnels"), + I2pStatus::Reject => String::from("Rejecting tunnels: Starting up"), + } + } +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct HttpProxyStatus { + pub open: bool +} + +#[derive(Debug)] +pub enum ProxyStatus { + Opening, + Open +} + +impl ProxyStatus { + pub fn value(&self) -> String { + match *self { + ProxyStatus::Opening => String::from("opening\n"), + ProxyStatus::Open => String::from("open\n"), + } + } +} + +#[derive(Serialize, Deserialize, Debug)] +struct Tunnel { + // http proxy tunnel wont have this field + dest: Option, + port: String, +} + +#[derive(Serialize, Deserialize, Debug)] +struct Tunnels { + tunnels: Vec, +} + +impl Default for Tunnels { + fn default() -> Self { + Tunnels { tunnels: Vec::new() } + } +} + +async fn find_tunnels() { + let app_port = utils::get_app_port(); + let file_path = format!( + "/home/{}/.i2p-zero/config/tunnels.json", + env::var("USER").unwrap_or(String::from("user")) + ); + let contents = fs::read_to_string(file_path).unwrap_or(utils::empty_string()); + debug!("i2p tunnels: {}", contents); + let has_app_tunnel = contents.contains(&format!("{}", app_port)); + // let has_http_tunnel = contents.contains("4444"); + if !has_app_tunnel { + tokio::time::sleep(Duration::new(120, 0)).await; + create_tunnel(); + } + // TODO(c2m): why is i2p-zero http proxy always giving "destination not found" error? + // if !has_http_tunnel { create_http_proxy(); } +} + +pub async fn start() { + info!("starting i2p-zero"); + let args = args::Args::parse(); + let path = args.i2p_zero_dir; + let output = Command::new(format!("{}/router/bin/i2p-zero", path)) + .spawn() + .expect("i2p-zero failed to start"); + debug!("{:?}", output.stdout); + find_tunnels().await; + { tokio::spawn(async { check_connection(true).await }); } +} + +fn create_tunnel() { + info!("creating tunnel"); + let output = Command::new("./i2p-zero-linux.v1.20/router/bin/tunnel-control.sh") + .args(["server.create", "127.0.0.1", &format!("{}",utils::get_app_port())]) + .spawn() + .expect("i2p-zero failed to create a tunnel"); + debug!("{:?}", output.stdout); +} + +// TODO(c2m): use i2p-zero http proxy +// fn create_http_proxy() { +// info!("creating http proxy"); +// let output = Command::new("./i2p-zero-linux.v1.20/router/bin/tunnel-control.sh") +// .args(["http.create", "4444"]) +// .spawn() +// .expect("i2p-zero failed to create a http proxy"); +// debug!("{:?}", output.stdout); +// } + +pub fn get_destination() -> String { + let file_path = format!( + "/home/{}/.i2p-zero/config/tunnels.json", + env::var("USER").unwrap_or(String::from("user")) + ); + let contents = fs::read_to_string(file_path).expect("read tunnels.json"); + let input = format!(r#"{contents}"#); + let mut j: Tunnels = serde_json::from_str(&input).unwrap_or(Default::default()); + let destination: String = j.tunnels.remove(0).dest.ok_or(utils::empty_string()) + .unwrap_or(utils::empty_string()); + destination +} + +pub async fn get_proxy_status() -> HttpProxyStatus { + check_connection(false).await +} + +/// Ping i2pd tunnels local server for status +async fn check_connection(bg: bool) -> HttpProxyStatus { + let client: reqwest::Client = reqwest::Client::new(); + let host: &str = "http://localhost:7657/tunnels"; + if bg { + let tick: std::sync::mpsc::Receiver<()> = schedule_recv::periodic_ms(60000); + loop { + tick.recv().unwrap(); + return process_connection_info(client, host).await; + } + } else { + return process_connection_info(client, host).await; + } +} + +async fn process_connection_info(client: Client, host: &str) -> HttpProxyStatus { + match client.get(host).send().await { + Ok(response) => { + // do some parsing here to check the status + let res = response.text().await; + match res { + Ok(res) => { + // split the html from the local i2p tunnels page + let split1 = res.split("

"); + let mut v1: Vec = split1.map(|s| String::from(s)).collect(); + let s1 = v1.remove(1); + let v2 = s1.split("

"); + let mut split2: Vec = v2.map(|s| String::from(s)).collect(); + let status: String = split2.remove(0); + if status == I2pStatus::Accept.value() { + info!("i2p is currently accepting tunnels"); + return HttpProxyStatus { open: true }; + } else if status == I2pStatus::Reject.value() { + info!("i2p is currently rejecting tunnels"); + return HttpProxyStatus { open: false }; + } else { + info!("i2p is unstable"); + return HttpProxyStatus { open: true }; + } + } + _ => { + error!("i2p status check failure"); + return HttpProxyStatus { open: false }; + } + } + } + Err(e) => { + warn!("i2p-zero http proxy is degraded"); + warn!("please install i2pd from geti2p.org"); + warn!("start i2p with /path-to-i2p/i2prouter start"); + error!("{}", e.to_string()); + return HttpProxyStatus { open: false }; + } + } +} diff --git a/nevmes-core/src/lib.rs b/nevmes-core/src/lib.rs new file mode 100644 index 0000000..3f8663f --- /dev/null +++ b/nevmes-core/src/lib.rs @@ -0,0 +1,18 @@ +pub mod args; // command line arguments +pub mod auth; // internal auth repo/service layer +pub mod contact; // contact repo/service layer +pub mod db; // lmdb interface +pub mod gpg; // gpgme interface +pub mod i2p; // i2p repo/service layer +pub mod message; // message repo/service layer +pub mod models; // db structs +pub mod monero; // monero-wallet-rpc interface +pub mod proof; // external auth/payment proof module +pub mod reqres; // http request/responses +pub mod utils; // misc. +pub mod user; // user rep/service layer + +pub const NEVMES_JWP_SECRET_KEY: &str = "NEVMES_JWP_SECRET_KEY"; +pub const NEVMES_JWT_SECRET_KEY: &str = "NEVMES_JWT_SECRET_KEY"; + +// DO NOT EDIT BELOW THIS LINE diff --git a/nevmes-core/src/message.rs b/nevmes-core/src/message.rs new file mode 100644 index 0000000..25c6d43 --- /dev/null +++ b/nevmes-core/src/message.rs @@ -0,0 +1,248 @@ +// Message repo/service layer +use crate::{contact, db, models::*, utils, reqres, i2p, gpg}; +use std::error::Error; +use log::{debug, error, info}; +use rocket::serde::json::Json; + +/// Create a new message +pub async fn create(m: Json, jwp: String) -> Message { + let f_mid: String = format!("m{}", utils::generate_rnd()); + info!("creating message: {}", &f_mid); + let created = chrono::offset::Utc::now().timestamp(); + // get contact public gpg key and encrypt the message + debug!("sending message: {:?}", &m); + let e_body = gpg::encrypt( + String::from(&m.to), &m.body).unwrap_or(Vec::new()); + let new_message = Message { + mid: String::from(&f_mid), + uid: String::from(&m.uid), + from: i2p::get_destination(), + body: e_body, + created, + to: String::from(&m.to), + }; + debug!("insert message: {:?}", &new_message); + let s = db::Interface::open(); + let k = &new_message.mid; + db::Interface::write(&s.env, &s.handle, k, &Message::to_db(&new_message)); + // in order to retrieve all message, write keys to with ml + let list_key = format!("ml"); + let r = db::Interface::read(&s.env, &s.handle, &String::from(&list_key)); + if r == utils::empty_string() { + debug!("creating message index"); + } + let msg_list = [r, String::from(&f_mid)].join(","); + debug!("writing message index {} for id: {}", msg_list, list_key); + db::Interface::write(&s.env, &s.handle, &String::from(list_key), &msg_list); + info!("attempting to send message"); + let send = send_message(&new_message, &jwp).await; + send.unwrap(); + new_message +} + +/// Rx message +pub async fn rx(m: Json) { + // don't allow messages from outside the contact list + let is_in_contact_list = contact::exists(&m.from); + if !is_in_contact_list { + return; + } + let f_mid: String = format!("m{}", utils::generate_rnd()); + let new_message = Message { + mid: String::from(&f_mid), + uid: String::from("rx"), + from: String::from(&m.from), + body: m.body.iter().cloned().collect(), + created: chrono::offset::Utc::now().timestamp(), + to: String::from(&m.to), + }; + debug!("insert message: {:?}", &new_message); + let s = db::Interface::open(); + let k = &new_message.mid; + db::Interface::write(&s.env, &s.handle, k, &Message::to_db(&new_message)); + // in order to retrieve all message, write keys to with rx + let list_key = format!("rx"); + let r = db::Interface::read(&s.env, &s.handle, &String::from(&list_key)); + if r == utils::empty_string() { + debug!("creating message index"); + } + let msg_list = [r, String::from(&f_mid)].join(","); + debug!("writing message index {} for {}", msg_list, list_key); + db::Interface::write(&s.env, &s.handle, &String::from(list_key), &msg_list); +} + +/// Message lookup +pub fn find(mid: &String) -> Message { + let s = db::Interface::open(); + let r = db::Interface::read(&s.env, &s.handle, &String::from(mid)); + if r == utils::empty_string() { + error!("message not found"); + return Default::default() + } + Message::from_db(String::from(mid), r) +} + +/// Message lookup +pub fn find_all() -> Vec { + let i_s = db::Interface::open(); + let i_list_key = format!("ml"); + let i_r = db::Interface::read(&i_s.env, &i_s.handle, &String::from(i_list_key)); + if i_r == utils::empty_string() { + error!("message index not found"); + } + let i_v_mid = i_r.split(","); + let i_v: Vec = i_v_mid.map(|s| String::from(s)).collect(); + let mut messages: Vec = Vec::new(); + for m in i_v { + let message: Message = find(&m); + if message.mid != utils::empty_string() { + messages.push(message); + } + } + let o_list_key = format!("rx"); + let o_s = db::Interface::open(); + let o_r = db::Interface::read(&o_s.env, &o_s.handle, &String::from(o_list_key)); + if o_r == utils::empty_string() { + error!("message index not found"); + } + let o_v_mid = o_r.split(","); + let o_v: Vec = o_v_mid.map(|s| String::from(s)).collect(); + for m in o_v { + let message: Message = find(&m); + if message.mid != utils::empty_string() { + messages.push(message); + } + } + messages +} + +/// Tx message +async fn send_message(out: &Message, jwp: &str) -> Result<(), Box> { + + // TODO(c2m): Error handling for http 402 status + let host = utils::get_i2p_http_proxy(); + let proxy = reqwest::Proxy::http(&host)?; + let client = reqwest::Client::builder().proxy(proxy).build(); + // check if the contact is online + let is_online: bool = is_contact_online(String::from(jwp)).await.unwrap_or(false); + if is_online { + return match client?.post(format!("http://{}/message/rx", out.to)) + .header("proof", jwp).json(&out).send().await { + Ok(response) => { + let res = response.text().await; + debug!("send response: {:?}", res); + match res { + Ok(r) => { + if r.contains("402") { error!("Payment required"); } + // remove the mid from fts if necessary + remove_from_retry(String::from(&out.mid)); + Ok(()) + }, + _ => Ok(()), + } + } + Err(e) => { + error!("failed to send message due to: {:?}", e); + Ok(()) + } + } + } else { + send_to_retry(String::from(&out.mid)).await; + Ok(()) + } +} + +/// Returns decrypted hex string of the encrypted message +pub fn decrypt_body(mid: String) -> reqres::DecryptedMessageBody { + let m = find(&mid); + let d = gpg::decrypt(&mid, &m.body).unwrap(); + let body = hex::encode(d); + reqres::DecryptedMessageBody { mid, body } +} + +/// Message deletion +pub fn delete(mid: &String) { + let s = db::Interface::open(); + db::Interface::delete(&s.env, &s.handle, &String::from(mid)); +} + +/// ping the contact health check over i2p +async fn is_contact_online(jwp: String) -> Result> { + let host = utils::get_i2p_http_proxy(); + let proxy = reqwest::Proxy::http(&host)?; + let client = reqwest::Client::builder().proxy(proxy).build(); + match client?.get(format!("http://{}/xmr/rpc/version", host)) + .header("proof", jwp).send().await { + Ok(response) => { + let res = response.json::().await; + debug!("check is contact online by version response: {:?}", res); + match res { + Ok(r) => { + if r.result.version == 0 { error!("Payment required"); } + if r.result.version != 0 { Ok(true) } else { Ok(false) } + }, + _ => Ok(false), + } + } + Err(e) => { + error!("failed to send message due to: {:?}", e); + Ok(false) + } + } +} + +/// stage message for async retry +async fn send_to_retry(mid: String) { + let s = db::Interface::open(); + // in order to retrieve FTS (failed-to-send), write keys to with fts + let list_key = format!("fts"); + let r = db::Interface::read(&s.env, &s.handle, &String::from(&list_key)); + if r == utils::empty_string() { + debug!("creating fts message index"); + } + let msg_list = [r, String::from(&mid)].join(","); + debug!("writing fts message index {} for id: {}", msg_list, list_key); + db::Interface::write(&s.env, &s.handle, &String::from(list_key), &msg_list); +} + +/// clear fts message from index +fn remove_from_retry(mid: String) { + let s = db::Interface::open(); + // in order to retrieve FTS (failed-to-send), write keys to with fts + let list_key = format!("fts"); + let r = db::Interface::read(&s.env, &s.handle, &String::from(&list_key)); + if r == utils::empty_string() { + debug!("fts is empty"); + } + let pre_v_fts = r.split(","); + let v: Vec = pre_v_fts.map(|s| if s != &mid { String::from(s)} else { utils::empty_string()} ).collect(); + let msg_list = v.join(","); + debug!("writing fts message index {} for id: {}", msg_list, list_key); + db::Interface::write(&s.env, &s.handle, &String::from(list_key), &msg_list); +} + +/// triggered on app startup, retries to send fts every minute +pub async fn retry_fts() { + let tick: std::sync::mpsc::Receiver<()> = schedule_recv::periodic_ms(60000); + loop { + tick.recv().unwrap(); + let s = db::Interface::open(); + let list_key = format!("fts"); + let r = db::Interface::read(&s.env, &s.handle, &String::from(list_key)); + if r == utils::empty_string() { + error!("fts message index not found"); + } + let v_mid = r.split(","); + let v: Vec = v_mid.map(|s| String::from(s)).collect(); + for m in v { + let message: Message = find(&m); + if message.mid != utils::empty_string() { + // fetch the jwp which just so happens to be cached by the client + let s = db::Interface::open(); + let k = format!("{}-{}", "gui-jwp", message.to); + let jwp = db::Interface::read(&s.env, &s.handle, &k); + send_message(&message, &jwp).await.unwrap(); + } + } + } +} diff --git a/nevmes-core/src/models.rs b/nevmes-core/src/models.rs new file mode 100644 index 0000000..bfbd6e5 --- /dev/null +++ b/nevmes-core/src/models.rs @@ -0,0 +1,208 @@ +use crate::utils; +use rocket::serde::{Serialize, Deserialize}; + +#[derive(Debug, Serialize, Deserialize)] +#[serde(crate = "rocket::serde")] +pub struct Authorization { + pub aid: String, + pub created: i64, + pub rnd: String, + pub token: String, + pub uid: String, + pub xmr_address: String, +} + +impl Default for Authorization { + fn default() -> Self { + Authorization { + aid: utils::empty_string(), + created: 0, + uid: utils::empty_string(), + rnd: utils::empty_string(), + token: utils::empty_string(), + xmr_address: utils::empty_string(), + } + } +} + +impl Authorization { + pub fn to_db(a: &Authorization) -> String { + format!( + "{}:{}:{}:{}:{}", + a.created, a.uid, a.rnd, a.token, a.xmr_address + ) + } + pub fn from_db(k: String, v: String) -> Authorization { + let values = v.split(":"); + let mut v: Vec = values.map(|s| String::from(s)).collect(); + let created_str = v.remove(0); + let created = match created_str.parse::() { + Ok(n) => n, + Err(_e) => 0, + }; + let uid = v.remove(0); + let rnd = v.remove(0); + let token = v.remove(0); + let xmr_address = v.remove(0); + Authorization { + aid: k, + created, + uid, + rnd, + token, + xmr_address, + } + } + pub fn update_uid(a: Authorization, uid: String) -> Authorization { + Authorization { + aid: a.aid, + created: a.created, + uid, + rnd: a.rnd, + token: a.token, + xmr_address: a.xmr_address, + } + } + pub fn update_expiration( + a: Authorization, + created: i64, + rnd: String, + token: String, + ) -> Authorization { + Authorization { + aid: a.aid, + created, + uid: a.uid, + rnd, + token, + xmr_address: a.xmr_address, + } + } +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(crate = "rocket::serde")] +pub struct Contact { + pub cid: String, + pub i2p_address: String, + pub xmr_address: String, + pub gpg_key: Vec, +} + +impl Default for Contact { + fn default() -> Self { + Contact { + cid: utils::empty_string(), + gpg_key: Vec::new(), + i2p_address: utils::empty_string(), + xmr_address: utils::empty_string(), + } + } +} + +impl Contact { + pub fn to_db(c: &Contact) -> String { + let gpg = hex::encode(&c.gpg_key); + format!("{}!{}!{}", gpg, c.i2p_address, c.xmr_address) + } + pub fn from_db(k: String, v: String) -> Contact { + let values = v.split("!"); + let mut v: Vec = values.map(|s| String::from(s)).collect(); + let gpg_key = hex::decode(v.remove(0)).unwrap_or(Vec::new()); + let i2p_address = v.remove(0); + let xmr_address = v.remove(0); + Contact { + cid: k, + gpg_key, + i2p_address, + xmr_address, + } + } +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(crate = "rocket::serde")] +pub struct Message { + pub mid: String, + pub uid: String, + pub body: Vec, + pub created: i64, + pub from: String, + pub to: String, +} + +impl Default for Message { + fn default() -> Self { + Message { + mid: utils::empty_string(), + uid: utils::empty_string(), + body: Vec::new(), + created: 0, + from: utils::empty_string(), + to: utils::empty_string(), + } + } +} + +impl Message { + pub fn to_db(m: &Message) -> String { + let body = hex::encode(&m.body); + format!("{}:{}:{}:{}:{}", m.uid, body, m.created, m.from, m.to) + } + pub fn from_db(k: String, v: String) -> Message { + let values = v.split(":"); + let mut v: Vec = values.map(|s| String::from(s)).collect(); + let uid = v.remove(0); + let body = hex::decode(v.remove(0)).unwrap_or(Vec::new()); + let created_str = v.remove(0); + let created = match created_str.parse::() { + Ok(n) => n, + Err(_e) => 0, + }; + let from = v.remove(0); + let to = v.remove(0); + Message { mid: k, uid, body, created, from, to, } + } +} + +#[derive(Debug, Deserialize)] +#[serde(crate = "rocket::serde")] +pub struct User { + pub uid: String, + pub xmr_address: String, + pub name: String, +} + +impl Default for User { + fn default() -> Self { + User { + uid: utils::empty_string(), + xmr_address: utils::empty_string(), + name: utils::empty_string(), + } + } +} + +impl User { + pub fn to_db(u: &User) -> String { + format!("{}:{}", u.name, u.xmr_address) + } + pub fn from_db(k: String, v: String) -> User { + let values = v.split(":"); + let mut v: Vec = values.map(|s| String::from(s)).collect(); + let name = v.remove(0); + let xmr_address = v.remove(0); + User { + uid: k, + name, + xmr_address, + } + } + pub fn update(u: User, name: String) -> User { + User { + uid: u.uid, + name, + xmr_address: u.xmr_address, + } + } +} diff --git a/nevmes-core/src/monero.rs b/nevmes-core/src/monero.rs new file mode 100644 index 0000000..340bcbc --- /dev/null +++ b/nevmes-core/src/monero.rs @@ -0,0 +1,805 @@ +use crate::{args, reqres, utils::{self, get_release_env, ReleaseEnvironment}, proof}; +use clap::Parser; +use diqwest::WithDigestAuth; +use log::{debug, error, info}; +use std::process::Command; + +/// Current xmr ring size updated here. +const RING_SIZE: u32 = 0x10; + +struct RpcLogin { + username: String, + credential: String, +} + +enum RpcFields { + Address, + Balance, + CheckTxProof, + Close, + Create, + Export, + Finalize, + GetTxProof, + GetTxById, + GetVersion, + Id, + Import, + JsonRpcVersion, + Make, + Open, + Prepare, + SignMultisig, + SweepAll, + Transfer, + ValidateAddress, + Verify, +} + +impl RpcFields { + pub fn value(&self) -> String { + match *self { + RpcFields::Address => String::from("get_address"), + RpcFields::Balance => String::from("get_balance"), + RpcFields::CheckTxProof => String::from("check_tx_proof"), + RpcFields::Close => String::from("close_wallet"), + RpcFields::Create => String::from("create_wallet"), + RpcFields::Export => String::from("export_multisig_info"), + RpcFields::Finalize => String::from("finalize_multisig"), + RpcFields::GetTxProof => String::from("get_tx_proof"), + RpcFields::GetTxById => String::from("get_transfer_by_txid"), + RpcFields::GetVersion => String::from("get_version"), + RpcFields::Id => String::from("0"), + RpcFields::Import => String::from("import_multisig_info"), + RpcFields::JsonRpcVersion => String::from("2.0"), + RpcFields::Make => String::from("make_multisig"), + RpcFields::Open => String::from("open_wallet"), + RpcFields::Prepare => String::from("prepare_multisig"), + RpcFields::SignMultisig => String::from("sign_multisig"), + RpcFields::SweepAll => String::from("sweep_all"), + RpcFields::Transfer => String::from("transfer"), + RpcFields::ValidateAddress => String::from("validate_address"), + RpcFields::Verify => String::from("verify"), + } + } +} + +enum DaemonFields { + GetInfo, + Id, + Version, +} + +impl DaemonFields { + pub fn value(&self) -> String { + match *self { + DaemonFields::GetInfo => String::from("get_info"), + DaemonFields::Id => String::from("0"), + DaemonFields::Version => String::from("2.0"), + } + } +} + +pub enum LockTimeLimit { + Blocks, +} + +impl LockTimeLimit { + pub fn value(&self) -> u64 { + match *self { LockTimeLimit::Blocks => 20, } + } +} + +/// Start monerod from the -`-monero-location` flag +/// +/// default: /home/$USER/monero-xxx-xxx +pub fn start_daemon() { + info!("starting monerod"); + let blockchain_dir = get_blockchain_dir(); + let bin_dir = get_monero_location(); + let release_env = get_release_env(); + if release_env == ReleaseEnvironment::Development { + let args = ["--data-dir", &blockchain_dir, "--stagenet", "--detach"]; + let output = Command::new(format!("{}/monerod", bin_dir)) + .args(args) + .spawn() + .expect("monerod failed to start"); + debug!("{:?}", output.stdout); + } else { + let args = ["--data-dir", &blockchain_dir, "--detach"]; + let output = Command::new(format!("{}/monerod", bin_dir)) + .args(args) + .spawn() + .expect("monerod failed to start"); + debug!("{:?}", output.stdout); + } +} + +/// Start monero-wallet-rpc +pub fn start_rpc() { + info!("starting monero-wallet-rpc"); + let bin_dir = get_monero_location(); + let port = get_rpc_port(); + let login = get_rpc_creds(); + let daemon_address = get_rpc_daemon(); + let rpc_login = format!("{}:{}", &login.username, &login.credential); + let mut wallet_dir = format!("/home/{}/.nevmes/stagenet/wallet/", + std::env::var("USER").unwrap_or(String::from("user")), + ); + let release_env = get_release_env(); + if release_env == ReleaseEnvironment::Development { + let args = [ + "--rpc-bind-port", &port, + "--wallet-dir", &wallet_dir, + "--rpc-login", &rpc_login, + "--daemon-address", &daemon_address, + "--stagenet" + ]; + let output = Command::new(format!("{}/monero-wallet-rpc", bin_dir)) + .args(args) + .spawn() + .expect("monero-wallet-rpc failed to start"); + debug!("{:?}", output.stdout); + } else { + wallet_dir = format!("/home/{}/.nevmes/wallet/", + std::env::var("USER").unwrap_or(String::from("user")), + ); + let args = ["--rpc-bind-port", &port, "--wallet-dir", &wallet_dir, + "--rpc-login", &rpc_login, "--daemon-address", &daemon_address]; + let output = Command::new(format!("{}/monero-wallet-rpc", bin_dir)) + .args(args) + .spawn() + .expect("monero-wallet-rpc failed to start"); + debug!("{:?}", output.stdout); + } +} + +fn get_rpc_port() -> String { + let args = args::Args::parse(); + let rpc = String::from(args.monero_rpc_host); + let values = rpc.split(":"); + let mut v: Vec = values.map(|s| String::from(s)).collect(); + let port = v.remove(2); + debug!("monero-wallet-rpc port: {}", port); + port +} + +/// Get monero rpc host from command line argument +fn get_blockchain_dir() -> String { + let args = args::Args::parse(); + String::from(args.monero_blockchain_dir) +} + +/// Get monero download location +fn get_monero_location() -> String { + let args = args::Args::parse(); + String::from(args.monero_location) +} + +/// Get monero rpc host from the `--monero-rpc-host` cli arg +fn get_rpc_host() -> String { + let args = args::Args::parse(); + let rpc = String::from(args.monero_rpc_host); + format!("{}/json_rpc", rpc) +} + +/// Get creds from the `--monero-rpc-daemon` cli arg +fn get_rpc_creds() -> RpcLogin { + let args = args::Args::parse(); + let username = String::from(args.monero_rpc_username); + let credential = String::from(args.monero_rpc_cred); + RpcLogin { username, credential } +} + +fn get_rpc_daemon() -> String { + let args = args::Args::parse(); + let daemon = String::from(args.monero_rpc_daemon); + format!("{}/json_rpc", daemon) +} + +/// Performs rpc 'get_version' method +pub async fn get_version() -> reqres::XmrRpcVersionResponse { + let client = reqwest::Client::new(); + let host = get_rpc_host(); + let req = reqres::XmrRpcRequest { + jsonrpc: RpcFields::JsonRpcVersion.value(), + id: RpcFields::Id.value(), + method: RpcFields::GetVersion.value(), + }; + let login: RpcLogin = get_rpc_creds(); + match client.post(host).json(&req) + .send_with_digest_auth(&login.username, &login.credential).await { + Ok(response) => { + let res = response.json::().await; + debug!("get version response: {:?}", res); + match res { + Ok(res) => res, + _ => Default::default(), + } + } + Err(_e) => Default::default(), + } +} + +/// Helper function for checking xmr rpc online during app startup +pub async fn check_rpc_connection() -> () { + let res: reqres::XmrRpcVersionResponse = get_version().await; + if res.result.version == 0 { + error!("failed to connect to monero-wallet-rpc"); + } +} + +/// Performs the xmr rpc 'verify' method +pub async fn verify_signature(address: String, data: String, signature: String) -> String { + info!("signature verification in progress"); + let client = reqwest::Client::new(); + let host = get_rpc_host(); + let params = reqres::XmrRpcVerifyParams { + address, + data, + signature, + }; + let req = reqres::XmrRpcVerifyRequest { + jsonrpc: RpcFields::JsonRpcVersion.value(), + id: RpcFields::Id.value(), + method: RpcFields::Verify.value(), + params, + }; + let login: RpcLogin = get_rpc_creds(); + match client.post(host).json(&req) + .send_with_digest_auth(&login.username, &login.credential).await { + Ok(response) => { + let res = response.json::().await; + debug!("verify response: {:?}", res); + match res { + Ok(res) => { + if res.result.good { + req.params.address + } else { + utils::ApplicationErrors::LoginError.value() + } + } + _ => utils::ApplicationErrors::LoginError.value(), + } + } + Err(_e) => utils::ApplicationErrors::LoginError.value(), + } +} + +/// Performs the xmr rpc 'create_wallet' method +pub async fn create_wallet(filename: String) -> bool { + info!("creating wallet: {}", &filename); + let client = reqwest::Client::new(); + let host = get_rpc_host(); + let params = reqres::XmrRpcCreateWalletParams { + filename, + language: String::from("English"), + }; + let req = reqres::XmrRpcCreateRequest { + jsonrpc: RpcFields::JsonRpcVersion.value(), + id: RpcFields::Id.value(), + method: RpcFields::Create.value(), + params, + }; + let login: RpcLogin = get_rpc_creds(); + match client.post(host).json(&req) + .send_with_digest_auth(&login.username, &login.credential).await { + Ok(response) => { + // The result from wallet creation is empty + let res = response.text().await; + debug!("create response: {:?}", res); + match res { + Ok(r) => { + if r.contains("-1") { + return false; + } + true + }, + _ => false, + } + } + Err(_) => false + } +} + +/// Performs the xmr rpc 'open_wallet' method +pub async fn open_wallet(filename: String) -> bool { + info!("opening wallet for {}", &filename); + let client = reqwest::Client::new(); + let host = get_rpc_host(); + let params = reqres::XmrRpcOpenWalletParams { + filename, + }; + let req = reqres::XmrRpcOpenRequest { + jsonrpc: RpcFields::JsonRpcVersion.value(), + id: RpcFields::Id.value(), + method: RpcFields::Open.value(), + params, + }; + debug!("open request: {:?}", req); + let login: RpcLogin = get_rpc_creds(); + match client.post(host).json(&req) + .send_with_digest_auth(&login.username, &login.credential).await { + Ok(response) => { + // The result from wallet operation is empty + let res = response.text().await; + debug!("open response: {:?}", res); + match res { + Ok(r) => { + if r.contains("-1") { + return false; + } + return true; + }, + _ => false, + } + } + Err(_) => false + } +} + +/// Performs the xmr rpc 'close_wallet' method +pub async fn close_wallet(filename: String) -> bool { + info!("closing wallet for {}", &filename); + let client = reqwest::Client::new(); + let host = get_rpc_host(); + let params = reqres::XmrRpcOpenWalletParams { + filename, + }; + let req = reqres::XmrRpcOpenRequest { + jsonrpc: RpcFields::JsonRpcVersion.value(), + id: RpcFields::Id.value(), + method: RpcFields::Close.value(), + params, + }; + let login: RpcLogin = get_rpc_creds(); + match client.post(host).json(&req) + .send_with_digest_auth(&login.username, &login.credential).await { + Ok(response) => { + // The result from wallet operation is empty + let res = response.text().await; + debug!("close response: {:?}", res); + match res { + Ok(_) => true, + _ => false, + } + } + Err(_) => false + } +} + +/// Performs the xmr rpc 'get_balance' method +pub async fn get_balance() -> reqres::XmrRpcBalanceResponse { + info!("fetching wallet balance"); + let client = reqwest::Client::new(); + let host = get_rpc_host(); + let params: reqres::XmrRpcBalanceParams = reqres::XmrRpcBalanceParams { + account_index: 0, address_indices: vec![0], all_accounts: false, strict: false, + }; + let req = reqres::XmrRpcBalanceRequest { + jsonrpc: RpcFields::JsonRpcVersion.value(), + id: RpcFields::Id.value(), + method: RpcFields::Balance.value(), + params, + }; + let login: RpcLogin = get_rpc_creds(); + match client.post(host).json(&req) + .send_with_digest_auth(&login.username, &login.credential).await { + Ok(response) => { + let res = response.json::().await; + debug!("balance response: {:?}", res); + match res { + Ok(res) => res, + _ => Default::default(), + } + } + Err(_) => Default::default() + } +} + +/// Performs the xmr rpc 'get_address' method +pub async fn get_address() -> reqres::XmrRpcAddressResponse { + info!("fetching wallet address"); + let client = reqwest::Client::new(); + let host = get_rpc_host(); + let params: reqres::XmrRpcAddressParams = reqres::XmrRpcAddressParams { + account_index: 0, address_index: vec![0], + }; + let req = reqres::XmrRpcAddressRequest { + jsonrpc: RpcFields::JsonRpcVersion.value(), + id: RpcFields::Id.value(), + method: RpcFields::Address.value(), + params, + }; + let login: RpcLogin = get_rpc_creds(); + match client.post(host).json(&req) + .send_with_digest_auth(&login.username, &login.credential).await { + Ok(response) => { + let res = response.json::().await; + debug!("address response: {:?}", res); + match res { + Ok(res) => res, + _ => Default::default(), + } + } + Err(_) => Default::default() + } +} + +/// Performs the xmr rpc 'get_address' method +pub async fn validate_address(address: &String) -> reqres::XmrRpcValidateAddressResponse { + info!("validating wallet address"); + let client = reqwest::Client::new(); + let host = get_rpc_host(); + let params: reqres::XmrRpcValidateAddressParams = reqres::XmrRpcValidateAddressParams { + address: String::from(address), any_net_type: false, allow_openalias: true, + }; + let req = reqres::XmrRpcValidateAddressRequest { + jsonrpc: RpcFields::JsonRpcVersion.value(), + id: RpcFields::Id.value(), + method: RpcFields::ValidateAddress.value(), + params, + }; + let login: RpcLogin = get_rpc_creds(); + match client.post(host).json(&req) + .send_with_digest_auth(&login.username, &login.credential).await { + Ok(response) => { + let res = response.json::().await; + debug!("validate_address response: {:?}", res); + match res { + Ok(res) => res, + _ => Default::default(), + } + } + Err(_) => Default::default() + } +} +// START Multisig + +/// Performs the xmr rpc 'prepare_multisig' method +pub async fn prepare_wallet() -> reqres::XmrRpcPrepareResponse { + info!("prepare msig wallet"); + let client = reqwest::Client::new(); + let host = get_rpc_host(); + let req = reqres::XmrRpcRequest { + jsonrpc: RpcFields::JsonRpcVersion.value(), + id: RpcFields::Id.value(), + method: RpcFields::Prepare.value(), + }; + let login: RpcLogin = get_rpc_creds(); + match client.post(host).json(&req) + .send_with_digest_auth(&login.username, &login.credential).await { + Ok(response) => { + let res = response.json::().await; + debug!("prepare response: {:?}", res); + match res { + Ok(res) => res, + _ => Default::default(), + } + } + Err(_) => Default::default() + } +} + +/// Performs the xmr rpc 'make_multisig' method +pub async fn make_wallet(info: Vec) -> reqres::XmrRpcMakeResponse { + info!("make msig wallet"); + let client = reqwest::Client::new(); + let host = get_rpc_host(); + let params = reqres::XmrRpcMakeParams { + multisig_info: info, + threshold: 2, + }; + let req = reqres::XmrRpcMakeRequest { + jsonrpc: RpcFields::JsonRpcVersion.value(), + id: RpcFields::Id.value(), + method: RpcFields::Make.value(), + params, + }; + let login: RpcLogin = get_rpc_creds(); + match client.post(host).json(&req) + .send_with_digest_auth(&login.username, &login.credential).await { + Ok(response) => { + let res = response.json::().await; + debug!("make response: {:?}", res); + match res { + Ok(res) => res, + _ => Default::default(), + } + } + Err(_) => Default::default() + } +} + +/// Performs the xmr rpc 'finalize_multisig' method +pub async fn finalize_wallet(info: Vec) -> reqres::XmrRpcFinalizeResponse { + info!("finalize msig wallet"); + let client = reqwest::Client::new(); + let host = get_rpc_host(); + let params = reqres::XmrRpcFinalizeParams { + multisig_info: info, + }; + let req = reqres::XmrRpcFinalizeRequest { + jsonrpc: RpcFields::JsonRpcVersion.value(), + id: RpcFields::Id.value(), + method: RpcFields::Finalize.value(), + params, + }; + let login: RpcLogin = get_rpc_creds(); + match client.post(host).json(&req) + .send_with_digest_auth(&login.username, &login.credential).await { + Ok(response) => { + let res = response.json::().await; + debug!("finalize response: {:?}", res); + match res { + Ok(res) => res, + _ => Default::default(), + } + } + Err(_) => Default::default() + } +} + +/// Performs the xmr rpc 'export_multisig_info' method +pub async fn export_multisig_info() -> reqres::XmrRpcExportResponse { + info!("export msig info"); + let client = reqwest::Client::new(); + let host = get_rpc_host(); + let req = reqres::XmrRpcRequest { + jsonrpc: RpcFields::JsonRpcVersion.value(), + id: RpcFields::Id.value(), + method: RpcFields::Export.value(), + }; + let login: RpcLogin = get_rpc_creds(); + match client.post(host).json(&req) + .send_with_digest_auth(&login.username, &login.credential).await { + Ok(response) => { + let res = response.json::().await; + debug!("export msig response: {:?}", res); + match res { + Ok(res) => res, + _ => Default::default(), + } + } + Err(_) => Default::default() + } +} + +/// Performs the xmr rpc 'import_multisig_info' method +pub async fn import_multisig_info(info: Vec) -> reqres::XmrRpcImportResponse { + info!("import msig wallet"); + let client = reqwest::Client::new(); + let host = get_rpc_host(); + let params = reqres::XmrRpcImportParams { + info, + }; + let req = reqres::XmrRpcImportRequest { + jsonrpc: RpcFields::JsonRpcVersion.value(), + id: RpcFields::Id.value(), + method: RpcFields::Import.value(), + params, + }; + let login: RpcLogin = get_rpc_creds(); + match client.post(host).json(&req) + .send_with_digest_auth(&login.username, &login.credential).await { + Ok(response) => { + let res = response.json::().await; + debug!("import msig info response: {:?}", res); + match res { + Ok(res) => res, + _ => Default::default(), + } + } + Err(_) => Default::default() + } +} + +/// Performs the xmr rpc 'sign_multisig' method +pub async fn sign_multisig(tx_data_hex: String) -> reqres::XmrRpcSignMultisigResponse { + info!("sign msig txset"); + let client = reqwest::Client::new(); + let host = get_rpc_host(); + let params = reqres::XmrRpcSignMultisigParams { + tx_data_hex, + }; + let req = reqres::XmrRpcSignMultisigRequest { + jsonrpc: RpcFields::JsonRpcVersion.value(), + id: RpcFields::Id.value(), + method: RpcFields::SignMultisig.value(), + params, + }; + let login: RpcLogin = get_rpc_creds(); + match client.post(host).json(&req) + .send_with_digest_auth(&login.username, &login.credential).await { + Ok(response) => { + let res = response.json::().await; + debug!("sign msig txset response: {:?}", res); + match res { + Ok(res) => res, + _ => Default::default(), + } + } + Err(_) => Default::default() + } +} +// END Multisig + +/// Performs the xmr rpc 'check_tx_proof' method +pub async fn check_tx_proof(txp: &proof::TxProof) -> reqres::XmrRpcCheckTxProofResponse { + info!("check_tx_proof proof: {:?}", txp); + let client = reqwest::Client::new(); + let host = get_rpc_host(); + let params: reqres::XmrRpcCheckTxProofParams = reqres::XmrRpcCheckTxProofParams { + address: String::from(&txp.address), + message: String::from(&txp.message), + signature: String::from(&txp.signature), + txid: String::from(&txp.hash), + }; + let req = reqres::XmrRpcCheckTxProofRequest { + jsonrpc: RpcFields::JsonRpcVersion.value(), + id: RpcFields::Id.value(), + method: RpcFields::CheckTxProof.value(), + params, + }; + let login: RpcLogin = get_rpc_creds(); + match client.post(host).json(&req) + .send_with_digest_auth(&login.username, &login.credential).await { + Ok(response) => { + let res = response.json::().await; + debug!("check_tx_proof response: {:?}", res); + match res { + Ok(res) => res, + _ => Default::default(), + } + } + Err(_) => Default::default() + } +} + +/// Performs the xmr rpc 'get_tx_proof' method +pub async fn get_tx_proof(ptxp: proof::TxProof) -> reqres::XmrRpcGetTxProofResponse { + info!("fetching proof: {:?}", &ptxp.hash); + let client = reqwest::Client::new(); + let host = get_rpc_host(); + let params: reqres::XmrRpcGetTxProofParams = reqres::XmrRpcGetTxProofParams { + address: String::from(&ptxp.address), + message: String::from(&ptxp.message), + txid: String::from(&ptxp.hash), + }; + let req = reqres::XmrRpcGetTxProofRequest { + jsonrpc: RpcFields::JsonRpcVersion.value(), + id: RpcFields::Id.value(), + method: RpcFields::GetTxProof.value(), + params, + }; + let login: RpcLogin = get_rpc_creds(); + match client.post(host).json(&req) + .send_with_digest_auth(&login.username, &login.credential).await { + Ok(response) => { + let res = response.json::().await; + debug!("get_tx_proof response: {:?}", res); + match res { + Ok(res) => res, + _ => Default::default(), + } + } + Err(_) => Default::default() + } +} + +/// Performs the xmr rpc 'get_transfer_by_txid' method +pub async fn get_transfer_by_txid(txid: &str) -> reqres::XmrRpcGetTxByIdResponse { + info!("fetching tx: {:?}", txid); + let client = reqwest::Client::new(); + let host = get_rpc_host(); + let params: reqres::XmrRpcGetTxByIdParams = reqres::XmrRpcGetTxByIdParams { + txid: String::from(txid) + }; + let req = reqres::XmrRpcGetTxByIdRequest { + jsonrpc: RpcFields::JsonRpcVersion.value(), + id: RpcFields::Id.value(), + method: RpcFields::GetTxById.value(), + params, + }; + let login: RpcLogin = get_rpc_creds(); + match client.post(host).json(&req) + .send_with_digest_auth(&login.username, &login.credential).await { + Ok(response) => { + let res = response.json::().await; + debug!("get_transfer_by_txid response: {:?}", res); + match res { + Ok(res) => res, + _ => Default::default(), + } + } + Err(_) => Default::default() + } +} + +/// Performs the xmr rpc 'transfer' method +pub async fn transfer(d: reqres::Destination) -> reqres::XmrRpcTransferResponse { + info!("transfer"); + let client = reqwest::Client::new(); + let host = get_rpc_host(); + let mut destinations: Vec = Vec::new(); + destinations.push(d); + let params: reqres::XmrRpcTransferParams = reqres::XmrRpcTransferParams { + account_index: 0, + destinations, + get_tx_key: false, + priority: 0, + ring_size: RING_SIZE, + subaddr_indices: vec![0], + }; + let req = reqres::XmrRpcTransfrerRequest { + jsonrpc: RpcFields::JsonRpcVersion.value(), + id: RpcFields::Id.value(), + method: RpcFields::Transfer.value(), + params, + }; + let login: RpcLogin = get_rpc_creds(); + match client.post(host).json(&req) + .send_with_digest_auth(&login.username, &login.credential).await { + Ok(response) => { + let res = response.json::().await; + debug!("{} response: {:?}", RpcFields::Transfer.value(), res); + match res { + Ok(res) => res, + _ => Default::default(), + } + } + Err(_) => Default::default() + } +} + +/// Performs the xmr rpc 'sweep_all' method +pub async fn sweep_all(address: String) -> reqres::XmrRpcSweepAllResponse { + info!("sweep_all"); + let client = reqwest::Client::new(); + let host = get_rpc_host(); + let params: reqres::XmrRpcSweepAllParams = reqres::XmrRpcSweepAllParams { address }; + let req = reqres::XmrRpcSweepAllRequest { + jsonrpc: RpcFields::JsonRpcVersion.value(), + id: RpcFields::Id.value(), + method: RpcFields::SweepAll.value(), + params, + }; + let login: RpcLogin = get_rpc_creds(); + match client.post(host).json(&req) + .send_with_digest_auth(&login.username, &login.credential).await { + Ok(response) => { + let res = response.json::().await; + debug!("{} response: {:?}", RpcFields::SweepAll.value(), res); + match res { + Ok(res) => res, + _ => Default::default(), + } + } + Err(_) => Default::default() + } +} + +// Daemon requests +//------------------------------------------------------------------- +/// Performs the xmr daemon 'get_info' method +pub async fn get_info() -> reqres::XmrDaemonGetInfoResponse { + info!("fetching daemon info"); + let client = reqwest::Client::new(); + let host = get_rpc_daemon(); + let req = reqres::XmrRpcRequest { + jsonrpc: DaemonFields::Version.value(), + id: DaemonFields::Id.value(), + method: DaemonFields::GetInfo.value(), + }; + match client.post(host).json(&req).send().await { + Ok(response) => { + let res = response.json::().await; + debug!("{} response: {:?}", DaemonFields::GetInfo.value(), res); + match res { + Ok(res) => res, + _ => Default::default(), + } + } + Err(_) => Default::default() + } +} diff --git a/nevmes-core/src/proof.rs b/nevmes-core/src/proof.rs new file mode 100644 index 0000000..923a26e --- /dev/null +++ b/nevmes-core/src/proof.rs @@ -0,0 +1,206 @@ +use crate::{monero, reqres, utils}; +use log::{error, info}; +use std::error::Error; +use rocket::http::Status; +use rocket::outcome::Outcome; +use rocket::request::FromRequest; +use rocket::{request, Request}; + +use hmac::{Hmac, Mac}; +use jwt::*; +use serde::{Deserialize, Serialize}; +use sha2::Sha512; +use std::collections::BTreeMap; + +#[derive(Debug, Deserialize, Serialize)] +pub struct TxProof { + pub address: String, + pub confirmations: u64, + pub hash: String, + pub message: String, + pub signature: String, +} + +impl Default for TxProof { + fn default() -> Self { + TxProof { + address: utils::empty_string(), + confirmations: 0, + hash: utils::empty_string(), + message: utils::empty_string(), + signature: utils::empty_string(), + } + } +} + +pub async fn create_invoice() -> reqres::Invoice { + info!("creating invoice"); + let m_address = monero::get_address().await; + let address = m_address.result.address; + let pay_threshold = utils::get_payment_threshold(); + let conf_threshold = utils::get_conf_threshold(); + reqres::Invoice { address, conf_threshold, pay_threshold } +} + +pub async fn create_jwp(proof: &TxProof) -> String { + info!("creating jwp"); + // validate the proof + let c_txp: TxProof = validate_proof(proof).await; + if c_txp.confirmations == 0 { + return utils::empty_string(); + } + let jwp_secret_key = utils::get_jwp_secret_key(); + let key: Hmac = Hmac::new_from_slice(&jwp_secret_key.as_bytes()).expect("hash"); + let header = Header { + algorithm: AlgorithmType::Hs512, + ..Default::default() + }; + let mut claims = BTreeMap::new(); + let address = &proof.address; + let hash = &proof.hash; + let expire = &format!("{}", utils::get_payment_threshold()); + let message = &proof.message; + let signature = &proof.signature; + claims.insert("address", address); + claims.insert("hash", hash); + claims.insert("expire", expire); + claims.insert("message", message); + claims.insert("signature", signature); + let token = Token::new(header, claims).sign_with_key(&key); + String::from(token.expect("expected token").as_str()) +} + +/// Send transaction proof to contact for JWP generation +pub async fn prove_payment(contact: String, txp: &TxProof) -> Result> { + // TODO(c2m): Error handling for http 402 status + let host = utils::get_i2p_http_proxy(); + let proxy = reqwest::Proxy::http(&host)?; + let client = reqwest::Client::builder().proxy(proxy).build(); + match client?.post(format!("http://{}/prove", contact)).json(txp).send().await { + Ok(response) => { + let res = response.json::().await; + log::debug!("prove payment response: {:?}", res); + match res { + Ok(r) => { + Ok(r) + }, + _ => Ok(Default::default()), + } + } + Err(e) => { + error!("failed to prove payment: {:?}", e); + Ok(Default::default()) + } + } +} + +/// # PaymentProof +/// +/// is a JWP (JSON Web Proof) with the contents: +/// +/// address: this server's xmr address +/// +/// hash: hash of the payment +/// +/// message: (optional) default: empty string +/// +/// signature: validates proof of payment +#[derive(Debug)] +pub struct PaymentProof(String); + +impl PaymentProof { pub fn get_jwp(self) -> String { self.0 } } + +#[derive(Debug)] +pub enum PaymentProofError { + Expired, + Missing, + Invalid, +} + +#[rocket::async_trait] +impl<'r> FromRequest<'r> for PaymentProof { + type Error = PaymentProofError; + + async fn from_request(request: &'r Request<'_>) -> request::Outcome { + let proof = request.headers().get_one("proof"); + let m_address: reqres::XmrRpcAddressResponse = monero::get_address().await; + let nevmes_address = m_address.result.address; + match proof { + Some(proof) => { + // check validity of address, payment amount and tx confirmations + let jwp_secret_key = utils::get_jwp_secret_key(); + let key: Hmac = Hmac::new_from_slice(&jwp_secret_key.as_bytes()).expect(""); + let jwp: Result< + Token, _>, + jwt::Error, + > = proof.verify_with_key(&key); + return match jwp { + Ok(j) => { + let claims = j.claims(); + let address = &claims["address"]; + if address != &nevmes_address { + return Outcome::Failure(( + Status::PaymentRequired, + PaymentProofError::Invalid, + )); + } + let hash = &claims["hash"]; + let message = &claims["message"]; + let signature = &claims["signature"]; + // verify proof + let txp: TxProof = TxProof { + address: String::from(address), + hash: String::from(hash), + confirmations: 0, + message: String::from(message), + signature: String::from(signature), + }; + let c_txp = validate_proof(&txp).await; + if c_txp.confirmations == 0 { + return Outcome::Failure(( + Status::PaymentRequired, + PaymentProofError::Invalid, + )); + } + // verify expiration + let expire = utils::get_conf_threshold(); + if c_txp.confirmations > expire { + return Outcome::Failure(( + Status::Unauthorized, + PaymentProofError::Expired, + )); + } + Outcome::Success(PaymentProof(String::from(proof))) + } + Err(e) => { + error!("jwp error: {:?}", e); + return Outcome::Failure((Status::PaymentRequired, PaymentProofError::Invalid)); + } + }; + } + None => Outcome::Failure((Status::PaymentRequired, PaymentProofError::Missing)), + } + } +} + +async fn validate_proof(txp: &TxProof) -> TxProof { + // verify unlock time isn't something funky (e.g. > 20) + let tx: reqres::XmrRpcGetTxByIdResponse = monero::get_transfer_by_txid(&txp.hash).await; + let unlock_time = tx.result.transfer.unlock_time; + let p = monero::check_tx_proof(txp).await; + let cth = utils::get_conf_threshold(); + let pth = utils::get_payment_threshold(); + let lgtm = p.result.good && !p.result.in_pool + && unlock_time < monero::LockTimeLimit::Blocks.value() + && p.result.confirmations < cth && p.result.received >= pth; + if lgtm { + return TxProof { + address: String::from(&txp.address), + hash: String::from(&txp.hash), + confirmations: p.result.confirmations, + message: String::from(&txp.message), + signature: String::from(&txp.signature) + } + } + Default::default() +} diff --git a/nevmes-core/src/reqres.rs b/nevmes-core/src/reqres.rs new file mode 100644 index 0000000..48d1623 --- /dev/null +++ b/nevmes-core/src/reqres.rs @@ -0,0 +1,843 @@ +use crate::utils; +use serde::{Deserialize, Serialize}; + +// All http requests and responses are here + +// START XMR Structs + +// params +#[derive(Deserialize, Serialize, Debug)] +pub struct XmrRpcValidateAddressParams { + pub address: String, + pub any_net_type: bool, + pub allow_openalias: bool, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct XmrRpcVerifyParams { + pub address: String, + pub data: String, + pub signature: String, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct XmrRpcCreateWalletParams { + pub filename: String, + pub language: String, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct XmrRpcOpenWalletParams { + pub filename: String, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct XmrRpcMakeParams { + pub multisig_info: Vec, + pub threshold: u8, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct XmrRpcFinalizeParams { + pub multisig_info: Vec, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct XmrRpcImportParams { + pub info: Vec, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct XmrRpcSignMultisigParams { + pub tx_data_hex: String, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct XmrRpcBalanceParams { + pub account_index: u8, + pub address_indices: Vec, + pub all_accounts: bool, + pub strict: bool, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct XmrRpcAddressParams { + pub account_index: u8, + pub address_index: Vec, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct XmrRpcCheckTxProofParams { + pub address: String, + pub message: String, + pub signature: String, + pub txid: String, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct XmrRpcTxProofParams { + pub address: String, + pub message: String, + pub txid: String, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct XmrRpcGetTxProofParams { + pub address: String, + pub message: String, + pub txid: String, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct XmrRpcGetTxByIdParams { + pub txid: String, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct Destination { + pub address: String, + pub amount: u128, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct XmrRpcTransferParams { + pub destinations: Vec, + pub account_index: u32, + pub subaddr_indices: Vec, + pub priority: u8, + pub ring_size: u32, + pub get_tx_key: bool, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct XmrRpcSweepAllParams { + pub address: String, +} + +// requests +#[derive(Deserialize, Serialize, Debug)] +pub struct XmrRpcValidateAddressRequest { + pub jsonrpc: String, + pub id: String, + pub method: String, + pub params: XmrRpcValidateAddressParams, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct XmrRpcCreateRequest { + pub jsonrpc: String, + pub id: String, + pub method: String, + pub params: XmrRpcCreateWalletParams, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct XmrRpcOpenRequest { + pub jsonrpc: String, + pub id: String, + pub method: String, + pub params: XmrRpcOpenWalletParams, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct XmrRpcRequest { + pub jsonrpc: String, + pub id: String, + pub method: String, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct XmrRpcAddressRequest { + pub jsonrpc: String, + pub id: String, + pub method: String, + pub params: XmrRpcAddressParams, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct XmrRpcBalanceRequest { + pub jsonrpc: String, + pub id: String, + pub method: String, + pub params: XmrRpcBalanceParams, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct XmrRpcMakeRequest { + pub jsonrpc: String, + pub id: String, + pub method: String, + pub params: XmrRpcMakeParams, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct XmrRpcFinalizeRequest { + pub jsonrpc: String, + pub id: String, + pub method: String, + pub params: XmrRpcFinalizeParams, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct XmrRpcImportRequest { + pub jsonrpc: String, + pub id: String, + pub method: String, + pub params: XmrRpcImportParams, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct XmrRpcSignMultisigRequest { + pub jsonrpc: String, + pub id: String, + pub method: String, + pub params: XmrRpcSignMultisigParams, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct XmrRpcVerifyRequest { + pub jsonrpc: String, + pub id: String, + pub method: String, + pub params: XmrRpcVerifyParams, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct XmrRpcCheckTxProofRequest { + pub jsonrpc: String, + pub id: String, + pub method: String, + pub params: XmrRpcCheckTxProofParams, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct XmrRpcGetTxProofRequest { + pub jsonrpc: String, + pub id: String, + pub method: String, + pub params: XmrRpcGetTxProofParams, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct XmrRpcGetTxByIdRequest { + pub jsonrpc: String, + pub id: String, + pub method: String, + pub params: XmrRpcGetTxByIdParams, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct XmrRpcTransfrerRequest { + pub jsonrpc: String, + pub id: String, + pub method: String, + pub params: XmrRpcTransferParams, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct XmrRpcSweepAllRequest { + pub jsonrpc: String, + pub id: String, + pub method: String, + pub params: XmrRpcSweepAllParams, +} + +// results +#[derive(Deserialize, Debug)] +pub struct XmrRpcValidateAddressResult { + pub integrated: bool, + pub nettype: String, + pub openalias_address: String, + pub subaddress: bool, + pub valid: bool, +} + +#[derive(Deserialize, Debug)] +pub struct XmrRpcVerifyResult { + pub good: bool, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct XmrRpcVersionResult { + pub version: u32, +} + +#[derive(Deserialize, Debug)] +pub struct XmrRpcFinalizeResult { + pub address: String, +} + +#[derive(Deserialize, Debug)] +pub struct XmrRpcPrepareResult { + pub multisig_info: String, +} + +#[derive(Deserialize, Debug)] +pub struct XmrRpcMakeResult { + pub address: String, + pub multisig_info: String, +} + +#[derive(Deserialize, Debug)] +pub struct XmrRpcExportResult { + pub info: String, +} + +#[derive(Deserialize, Debug)] +pub struct XmrRpcImportResult { + pub n_outputs: u8, +} + +#[derive(Deserialize, Debug)] +pub struct XmrRpcSignMultisigResult { + pub tx_hash_list: Vec, +} + +#[derive(Deserialize, Debug)] +pub struct SubAddressInfo { + pub account_index: u8, + pub address_index: u8, + pub address: String, + pub balance: u128, + pub unlocked_balance: u128, + pub label: String, + pub num_unspent_outputs: u8, + pub time_to_unlock: u128, + pub blocks_to_unlock: u128, +} + +#[derive(Deserialize, Debug)] +pub struct Address { + pub address: String, + pub address_index: u8, + pub label: String, + pub used: bool, +} + +#[derive(Deserialize, Debug)] +pub struct XmrRpcAddressResult { + pub address: String, + pub addresses: Vec
, +} + +#[derive(Deserialize, Debug)] +pub struct XmrRpcBalanceResult { + pub balance: u128, + pub unlocked_balance: u128, + pub multisig_import_needed: bool, + pub time_to_unlock: u128, + pub blocks_to_unlock: u128, + pub per_subaddress: Vec, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct XmrRpcCheckTxProofResult { + pub confirmations: u64, + pub good: bool, + pub in_pool: bool, + pub received: u128, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct XmrRpcGetTxProofResult { + pub signature: String, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct SubAddressIndex { + pub major: u64, + pub minor: u64, +} + +impl Default for SubAddressIndex { + fn default() -> Self { + SubAddressIndex { + major: 0, + minor: 0, + } + } +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct Transfer { + pub address: String, + pub amount: u128, + pub amounts: Vec, + pub confirmations: u64, + pub double_spend_seen: bool, + pub fee: u128, + pub height: u64, + pub locked: bool, + pub note: String, + pub payment_id: String, + pub subaddr_index: SubAddressIndex, + pub subaddr_indices: Vec, + pub suggested_confirmations_threshold: u64, + pub timestamp: u64, + pub txid: String, + pub r#type: String, + pub unlock_time: u64, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct XmrRpcGetTxByIdResult { + pub transfer: Transfer, + pub transfers: Vec, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct XmrRpcTranferResult { + pub amount: u128, + pub fee: u128, + pub multisig_txset: String, + pub tx_blob: String, + pub tx_hash: String, + pub tx_key: String, + pub tx_metadata: String, + pub unsigned_txset: String, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct KeyImageList { + key_images: Vec, +} +#[derive(Deserialize, Serialize, Debug)] +pub struct XmrRpcSweepAllResult { + pub amount_list: Vec, + pub fee_list: Vec, + pub multisig_txset: String, + pub spent_key_images_list: Vec, + pub tx_hash_list: Vec, + pub unsigned_txset: String, + pub weight_list: Vec, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct XmrDaemonGetInfoResult { + pub adjusted_time: u64, + pub alt_blocks_count: u64, + pub block_size_limit: u64, + pub block_size_median: u64, + pub block_weight_median: u64, + pub bootstrap_daemon_address: String, + pub busy_syncing: bool, + pub credits: u64, + pub cumulative_difficulty: u64, + pub cumulative_difficulty_top64: u64, + pub database_size: u64, + pub difficulty: u64, + pub difficulty_top64: u64, + pub free_space: u64, + pub grey_peerlist_size: u64, + pub height: u64, + pub height_without_bootstrap: u64, + pub incoming_connections_count: u32, + pub mainnet: bool, + pub nettype: String, + pub offline: bool, + pub outgoing_connections_count: u32, + pub restricted: bool, + pub rpc_connections_count: u32, + pub stagenet: bool, + pub start_time: u64, + pub status: String, + pub synchronized: bool, + pub target: u32, + pub target_height: u32, + pub testnet: bool, + pub top_block_hash: String, + pub top_hash: String, + pub tx_count: u64, + pub tx_pool_size: u32, + pub untrusted: bool, + pub update_available: bool, + pub version: String, + pub was_bootstrap_ever_used: bool, + pub white_peerlist_size: u32, + pub wide_cumulative_difficulty: String, + pub wide_difficulty: String, +} + +// responses +#[derive(Serialize, Deserialize, Debug)] +pub struct XmrDaemonGetInfoResponse { + pub result: XmrDaemonGetInfoResult, +} + +impl Default for XmrDaemonGetInfoResponse { + fn default() -> Self { + XmrDaemonGetInfoResponse { + result: XmrDaemonGetInfoResult { + adjusted_time: 0, + alt_blocks_count: 0, + block_size_limit: 0, + block_size_median: 0, + block_weight_median: 0, + bootstrap_daemon_address: utils::empty_string(), + busy_syncing: false, + credits: 0, + cumulative_difficulty: 0, + cumulative_difficulty_top64: 0, + database_size: 0, + difficulty: 0, + difficulty_top64: 0, + free_space: 0, + grey_peerlist_size: 0, + height: 0, + height_without_bootstrap: 0, + incoming_connections_count: 0, + mainnet: false, + nettype: utils::empty_string(), + offline: false, + outgoing_connections_count: 0, + restricted: false, + rpc_connections_count: 0, + stagenet: false, + start_time: 0, + status: utils::empty_string(), + synchronized: false, + target: 0, + target_height: 0, + testnet: false, + top_block_hash: utils::empty_string(), + top_hash: utils::empty_string(), + tx_count: 0, + tx_pool_size: 0, + untrusted: false, + update_available: false, + version: utils::empty_string(), + was_bootstrap_ever_used: false, + white_peerlist_size: 0, + wide_cumulative_difficulty: utils::empty_string(), + wide_difficulty: utils::empty_string(), + }, + } + } +} + +#[derive(Deserialize, Debug)] +pub struct XmrRpcVerifyResponse { + pub result: XmrRpcVerifyResult, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct XmrRpcVersionResponse { + pub result: XmrRpcVersionResult, +} + +impl Default for XmrRpcVersionResponse { + fn default() -> Self { + XmrRpcVersionResponse { + result: XmrRpcVersionResult { version: 0 }, + } + } +} + +#[derive(Deserialize, Debug)] +pub struct XmrRpcPrepareResponse { + pub result: XmrRpcPrepareResult, +} + +impl Default for XmrRpcPrepareResponse { + fn default() -> Self { + XmrRpcPrepareResponse { + result: XmrRpcPrepareResult { + multisig_info: String::from(""), + }, + } + } +} + +#[derive(Deserialize, Debug)] +pub struct XmrRpcBalanceResponse { + pub result: XmrRpcBalanceResult, +} + +impl Default for XmrRpcBalanceResponse { + fn default() -> Self { + XmrRpcBalanceResponse { + result: XmrRpcBalanceResult { + balance: 0, + unlocked_balance: 0, + multisig_import_needed: false, + time_to_unlock: 0, + blocks_to_unlock: 0, + per_subaddress: Vec::new(), + }, + } + } +} + +#[derive(Deserialize, Debug)] +pub struct XmrRpcValidateAddressResponse { + pub result: XmrRpcValidateAddressResult, +} + +impl Default for XmrRpcValidateAddressResponse { + fn default() -> Self { + XmrRpcValidateAddressResponse { + result: XmrRpcValidateAddressResult { + integrated: false, + nettype: utils::empty_string(), + openalias_address: utils::empty_string(), + subaddress: false, + valid: false, + }, + } + } +} + +#[derive(Deserialize, Debug)] +pub struct XmrRpcAddressResponse { + pub result: XmrRpcAddressResult, +} + +impl Default for XmrRpcAddressResponse { + fn default() -> Self { + XmrRpcAddressResponse { + result: XmrRpcAddressResult { + address: utils::empty_string(), + addresses: Vec::new(), + }, + } + } +} + +#[derive(Deserialize, Debug)] +pub struct XmrRpcMakeResponse { + pub result: XmrRpcMakeResult, +} + +impl Default for XmrRpcMakeResponse { + fn default() -> Self { + XmrRpcMakeResponse { + result: XmrRpcMakeResult { + address: utils::empty_string(), + multisig_info: utils::empty_string(), + }, + } + } +} + +#[derive(Deserialize, Debug)] +pub struct XmrRpcFinalizeResponse { + pub result: XmrRpcFinalizeResult, +} + +impl Default for XmrRpcFinalizeResponse { + fn default() -> Self { + XmrRpcFinalizeResponse { + result: XmrRpcFinalizeResult { + address: utils::empty_string(), + }, + } + } +} + +#[derive(Deserialize, Debug)] +pub struct XmrRpcExportResponse { + pub result: XmrRpcExportResult, +} + +impl Default for XmrRpcExportResponse { + fn default() -> Self { + XmrRpcExportResponse { + result: XmrRpcExportResult { + info: utils::empty_string(), + }, + } + } +} + +#[derive(Deserialize, Debug)] +pub struct XmrRpcImportResponse { + pub result: XmrRpcImportResult, +} + +impl Default for XmrRpcImportResponse { + fn default() -> Self { + XmrRpcImportResponse { + result: XmrRpcImportResult { n_outputs: 0 }, + } + } +} + +#[derive(Deserialize, Debug)] +pub struct XmrRpcSignMultisigResponse { + pub result: XmrRpcSignMultisigResult, +} + +impl Default for XmrRpcSignMultisigResponse { + fn default() -> Self { + XmrRpcSignMultisigResponse { + result: XmrRpcSignMultisigResult { + tx_hash_list: Vec::new(), + }, + } + } +} + +#[derive(Deserialize, Debug)] +pub struct XmrRpcCheckTxProofResponse { + pub result: XmrRpcCheckTxProofResult, +} + +impl Default for XmrRpcCheckTxProofResponse { + fn default() -> Self { + XmrRpcCheckTxProofResponse { + result: XmrRpcCheckTxProofResult { + confirmations: 0, + good: false, + in_pool: false, + received: 0, + }, + } + } +} + +#[derive(Deserialize, Debug)] +pub struct XmrRpcGetTxProofResponse { + pub result: XmrRpcGetTxProofResult, +} + +impl Default for XmrRpcGetTxProofResponse { + fn default() -> Self { + XmrRpcGetTxProofResponse { + result: XmrRpcGetTxProofResult { + signature: utils::empty_string(), + }, + } + } +} + +#[derive(Deserialize, Debug)] +pub struct XmrRpcGetTxByIdResponse { + pub result: XmrRpcGetTxByIdResult, +} + +impl Default for XmrRpcGetTxByIdResponse { + fn default() -> Self { + XmrRpcGetTxByIdResponse { + result: XmrRpcGetTxByIdResult { + transfer: Transfer { + address: utils::empty_string(), + amount: 0, + amounts: Vec::new(), + confirmations: 0, + double_spend_seen: false, + fee: 0, + height: 0, + locked: false, + note: utils::empty_string(), + payment_id: utils::empty_string(), + subaddr_index: Default::default(), + subaddr_indices: Vec::new(), + suggested_confirmations_threshold: 0, + timestamp: 0, + txid: utils::empty_string(), + r#type: utils::empty_string(), + unlock_time: 0, + }, + transfers: Vec::new(), + }, + } + } +} + +#[derive(Deserialize, Debug)] +pub struct XmrRpcTransferResponse { + pub result: XmrRpcTranferResult, +} + +impl Default for XmrRpcTransferResponse { + fn default() -> Self { + XmrRpcTransferResponse { + result: XmrRpcTranferResult { + amount: 0, + fee: 0, + multisig_txset: utils::empty_string(), + tx_blob: utils::empty_string(), + tx_hash: utils::empty_string(), + tx_key: utils::empty_string(), + tx_metadata: utils::empty_string(), + unsigned_txset: utils::empty_string(), + }, + } + } +} + +#[derive(Deserialize, Debug)] +pub struct XmrRpcSweepAllResponse { + pub result: XmrRpcSweepAllResult, +} + +impl Default for XmrRpcSweepAllResponse { + fn default() -> Self { + XmrRpcSweepAllResponse { + result: XmrRpcSweepAllResult { + amount_list: Vec::new(), + fee_list: Vec::new(), + multisig_txset: utils::empty_string(), + spent_key_images_list: Vec::new(), + tx_hash_list: Vec::new(), + unsigned_txset: utils::empty_string(), + weight_list: Vec::new(), + }, + } + } +} +// END XMR Structs + + +/// Container for the message decryption +#[derive(Serialize, Deserialize)] +#[serde(crate = "rocket::serde")] +pub struct DecryptedMessageBody { + pub mid: String, + pub body: String, +} + +impl Default for DecryptedMessageBody { + fn default() -> Self { + DecryptedMessageBody { + mid: utils::empty_string(), + body: utils::empty_string(), + } + } +} + +/// Invoice response for host.b32.i2p/invoice +#[derive(Serialize, Deserialize, Debug)] +#[serde(crate = "rocket::serde")] +pub struct Invoice { + pub address: String, + pub pay_threshold: u128, + pub conf_threshold: u64, +} + +impl Default for Invoice { + fn default() -> Self { + Invoice { + address: utils::empty_string(), + pay_threshold: 0, + conf_threshold: 0, + } + } +} + +/// Not to be confused with the PaymentProof guard. +/// +/// This is the response when proving payment +#[derive(Serialize, Deserialize, Debug)] +#[serde(crate = "rocket::serde")] +pub struct Jwp { + pub jwp: String, +} + +impl Default for Jwp { + fn default() -> Self { + Jwp { + jwp: utils::empty_string(), + } + } +} diff --git a/nevmes-core/src/user.rs b/nevmes-core/src/user.rs new file mode 100644 index 0000000..df65a2d --- /dev/null +++ b/nevmes-core/src/user.rs @@ -0,0 +1,47 @@ +// User repo/service layer +use crate::{db, models::*, utils}; +use rocket::serde::json::Json; +use log::{debug, error, info}; + +// This module is only used for remote access + +/// Create a new user +pub fn create(address: &String) -> User { + let f_uid: String = format!("u{}", utils::generate_rnd()); + let new_user = User { + uid: String::from(&f_uid), + xmr_address: String::from(address), + name: utils::empty_string(), + }; + debug!("insert user: {:?}", &new_user); + let s = db::Interface::open(); + let k = &new_user.uid; + db::Interface::write(&s.env, &s.handle, k, &User::to_db(&new_user)); + new_user +} + +/// User lookup +pub fn find(uid: &String) -> User { + let s = db::Interface::open(); + let r = db::Interface::read(&s.env, &s.handle, &String::from(uid)); + if r == utils::empty_string() { + error!("user not found"); + return Default::default() + } + User::from_db(String::from(uid), r) +} + +/// Modify user +pub fn modify(u: Json) -> User { + info!("modify user: {}", u.uid); + let f_cust: User = find(&u.uid); + if f_cust.uid == utils::empty_string() { + error!("user not found"); + return Default::default(); + } + let u_user = User::update(f_cust, String::from(&u.name)); + let s = db::Interface::open(); + db::Interface::delete(&s.env, &s.handle, &u_user.uid); + db::Interface::write(&s.env, &s.handle, &u_user.uid, &User::to_db(&u_user)); + return u_user; +} diff --git a/nevmes-core/src/utils.rs b/nevmes-core/src/utils.rs new file mode 100644 index 0000000..095b763 --- /dev/null +++ b/nevmes-core/src/utils.rs @@ -0,0 +1,366 @@ +use rand_core::RngCore; +use clap::Parser; +use rocket::serde::json::Json; +use crate::{args, db, i2p, message, models, monero, gpg, utils, reqres}; +use log::{info, debug, error, warn}; +use std::time::Duration; + +/// Handles the state for the connection manager popup +pub struct Connections { + pub blockchain_dir: String, + pub daemon_host: String, + pub i2p_zero_dir: String, + pub mainnet: bool, + pub monero_location: String, + pub rpc_credential: String, + pub rpc_username: String, + pub rpc_host: String, +} + +impl Default for Connections { + fn default() -> Self { + Connections { + blockchain_dir: String::from("/home/user/.bitmonero"), + daemon_host: String::from("http://localhost:38081"), + i2p_zero_dir: String::from("/home/user/i2p-zero-linux.v1.20"), + mainnet: false, + monero_location: String::from("/home/user/monero-x86_64-linux-gnu-v0.18.2.2"), + rpc_credential: String::from("pass"), + rpc_username: String::from("user"), + rpc_host: String::from("http://localhost:38083"), + } + } +} + +#[derive(Debug)] +pub enum ApplicationErrors { + LoginError, + UnknownError, +} + +impl ApplicationErrors { + pub fn value(&self) -> String { + match *self { + ApplicationErrors::LoginError => String::from("LoginError"), + ApplicationErrors::UnknownError => String::from("UnknownError"), + } + } +} + +#[derive(Debug, PartialEq)] +pub enum ReleaseEnvironment { + Development, + Production, +} + +impl ReleaseEnvironment { + pub fn value(&self) -> String { + match *self { + ReleaseEnvironment::Development => String::from("development"), + ReleaseEnvironment::Production => String::from("production"), + } + } +} + +/// start core module from gui +pub fn start_core(conn: &Connections) { + let env = if !conn.mainnet { "dev" } else { "prod" }; + let args = [ + "--monero-location", &conn.monero_location, + "--monero-blockchain-dir", &conn.blockchain_dir, + "--monero-rpc-host", &conn.rpc_host, + "--monero-rpc-daemon", &conn.daemon_host, + "--monero-rpc-username", &conn.rpc_username, + "--monero-rpc-cred", &conn.rpc_credential, + "--i2p-zero-dir", &conn.i2p_zero_dir, + "-r", env, + ]; + let path = if conn.mainnet { "nevmes" } else { "../target/debug/nevmes" }; + let output = std::process::Command::new(path) + .args(args) + .spawn() + .expect("core module failed to start"); + debug!("{:?}", output.stdout); +} + +/// Using remote node? +pub fn is_using_remote_node() -> bool { + let args = args::Args::parse(); + let r = args.remote_node; + if r { warn!("using a remote node may harm privacy"); } + r +} + +/// Random data generation for auth / primary keys +pub fn generate_rnd() -> String { + let mut data = [0u8; 32]; + rand::thread_rng().fill_bytes(&mut data); + hex::encode(data) +} + +/// Helper for separation of dev and prod concerns +pub fn get_release_env() -> ReleaseEnvironment { + let args = args::Args::parse(); + let env = String::from(args.release_env); + if env == "prod" { + return ReleaseEnvironment::Production; + } else { + return ReleaseEnvironment::Development; + } +} + +/// app port +pub fn get_app_port() -> u16 { + let args = args::Args::parse(); + args.port +} + +/// i2p http proxy +pub fn get_i2p_http_proxy() -> String { + let args = args::Args::parse(); + args.i2p_proxy_host +} + +/// app auth port +pub fn get_app_auth_port() -> u16 { + let args = args::Args::parse(); + args.auth_port +} + +/// app contact port +pub fn get_app_contact_port() -> u16 { + let args = args::Args::parse(); + args.contact_port +} + +/// app message port +pub fn get_app_message_port() -> u16 { + let args = args::Args::parse(); + args.message_port +} + +/// jwp confirmation limit +pub fn get_conf_threshold() -> u64 { + let args = args::Args::parse(); + args.confirmation_threshold +} + +/// jwp confirmation limit +pub fn get_payment_threshold() -> u128 { + let args = args::Args::parse(); + args.payment_threshold +} + +/// convert contact to json so only core module does the work +pub fn contact_to_json(c: &models::Contact) -> Json { + let r_contact: models::Contact = models::Contact { + cid: String::from(&c.cid), + i2p_address: String::from(&c.i2p_address), + xmr_address: String::from(&c.xmr_address), + gpg_key: c.gpg_key.iter().cloned().collect(), + }; + Json(r_contact) +} + +/// convert message to json so only core module does the work +pub fn message_to_json(m: &models::Message) -> Json { + let r_message: models::Message = models::Message { + body: m.body.iter().cloned().collect(), + mid: String::from(&m.mid), + uid: utils::empty_string(), + created: m.created, + from: String::from(&m.from), + to: String::from(&m.to), + }; + Json(r_message) +} + +/// Instead of putting `String::from("")` +pub fn empty_string() -> String { String::from("") } + +// DoS prevention +pub const fn string_limit() -> usize { 512 } +pub const fn gpg_key_limit() -> usize { 4096 } +pub const fn message_limit() -> usize { 9999 } + +/// Generate application gpg keys at startup if none exist +async fn gen_app_gpg() { + let mut gpg_key = gpg::find_key().unwrap_or(utils::empty_string()); + if gpg_key == utils::empty_string() { + info!("no gpg key found for nevmes, creating it..."); + // wait for key gen + gpg::write_gen_batch().unwrap(); + gpg::gen_key(); + tokio::time::sleep(Duration::new(9, 0)).await; + gpg_key = gpg::find_key().unwrap_or(utils::empty_string()); + } + debug!("gpg key: {}", gpg_key); +} + +/// Generate application wallet at startup if none exist +async fn gen_app_wallet() { + info!("fetching application wallet"); + let filename = "nevmes"; + let mut m_wallet = monero::open_wallet(String::from(filename)).await; + if !m_wallet { + m_wallet = monero::create_wallet(String::from(filename)).await; + if !m_wallet { + error!("failed to create wallet") + } else { + m_wallet = monero::open_wallet(String::from(filename)).await; + if m_wallet { + let m_address: reqres::XmrRpcAddressResponse = + monero::get_address().await; + info!("app wallet address: {}", m_address.result.address) + } + } + } +} + +/// Secret keys for signing internal/external auth tokens +fn gen_signing_keys() { + info!("generating signing keys"); + let jwp = get_jwp_secret_key(); + let jwt = get_jwt_secret_key(); + // send to db + let s = db::Interface::open(); + if jwp == utils::empty_string() { + let rnd_jwp = generate_rnd(); + db::Interface::write(&s.env, &s.handle, crate::NEVMES_JWP_SECRET_KEY, &rnd_jwp); + } + if jwt == utils::empty_string() { + let rnd_jwt = generate_rnd(); + db::Interface::write(&s.env, &s.handle, crate::NEVMES_JWT_SECRET_KEY, &rnd_jwt); + } +} + +/// TODO(c2m): add a button to gui to call this +/// +/// dont' forget to generate new keys as well +pub fn revoke_signing_keys() { + let s = db::Interface::open(); + db::Interface::delete(&s.env, &s.handle, crate::NEVMES_JWT_SECRET_KEY); + db::Interface::delete(&s.env, &s.handle, crate::NEVMES_JWP_SECRET_KEY); +} + +pub fn get_jwt_secret_key() -> String { + let s = db::Interface::open(); + let r = db::Interface::read(&s.env, &s.handle, crate::NEVMES_JWT_SECRET_KEY); + if r == utils::empty_string() { + error!("JWT key not found"); + return Default::default() + } + r +} + +pub fn get_jwp_secret_key() -> String { + let s = db::Interface::open(); + let r = db::Interface::read(&s.env, &s.handle, crate::NEVMES_JWP_SECRET_KEY); + if r == utils::empty_string() { + error!("JWP key not found"); + return Default::default() + } + r +} + +/// Start the remote access microservers `--remote-access` flag +fn start_micro_servers() { + info!("starting auth server"); + let mut auth_path = "nevmes-auth/target/debug/nevmes_auth"; + let env = get_release_env(); + if env == ReleaseEnvironment::Production { auth_path = "nevmes_auth"; } + let a_output = std::process::Command::new(auth_path) + .spawn().expect("failed to start auth server"); + debug!("{:?}", a_output.stdout); + info!("starting contact server"); + let mut contact_path = "nevmes-contact/target/debug/nevmes_contact"; + if env == ReleaseEnvironment::Production { contact_path = "nevmes_contact"; } + let c_output = std::process::Command::new(contact_path) + .spawn().expect("failed to start contact server"); + debug!("{:?}", c_output.stdout); + info!("starting message server"); + let mut message_path = "nevmes-message/target/debug/nevmes_message"; + if env == ReleaseEnvironment::Production { message_path = "nevmes_message"; } + let m_output = std::process::Command::new(message_path) + .spawn().expect("failed to start message server"); + debug!("{:?}", m_output.stdout); +} + +/// open gui from i2m core launch +fn start_gui() { + let args = args::Args::parse(); + if args.gui { + info!("starting gui"); + let mut gui_path = "nevmes-gui/target/debug/nevmes_gui"; + let env = get_release_env(); + if env == ReleaseEnvironment::Production { gui_path = "nevmes-gui"; } + let g_output = std::process::Command::new(gui_path) + .spawn().expect("failed to start gui"); + debug!("{:?}", g_output.stdout); + } +} + +/// Put all app pre-checks here +pub async fn start_up() { + info!("nevmes is starting up"); + let args = args::Args::parse(); + if args.remote_access { start_micro_servers(); } + gen_signing_keys(); + if !is_using_remote_node() { monero::start_daemon(); } + // wait for daemon for a bit + tokio::time::sleep(std::time::Duration::new(5, 0)).await; + monero::start_rpc(); + // wait for rpc server for a bit + tokio::time::sleep(std::time::Duration::new(5, 0)).await; + monero::check_rpc_connection().await; + gen_app_wallet().await; + i2p::start().await; + gen_app_gpg().await; + let env: String = get_release_env().value(); + start_gui(); + { tokio::spawn(async { message::retry_fts().await; }); } + info!("{} - nevmes is online", env); +} + +/// Called by gui for cleaning up monerod, rpc, etc. +/// +/// pass true from gui connection manager so not to kill nevmes +pub fn kill_child_processes(cm: bool) { + info!("stopping child processes"); + // TODO(c2m): prompt on gui letting user determine what background + // services to keep running + if cm { + let xmrd_output = std::process::Command::new("pkill") + .arg("monerod") + .spawn() + .expect("monerod failed to stop"); + debug!("{:?}", xmrd_output.stdout); + let rpc_output = std::process::Command::new("killall") + .arg("monero-wallet-rpc") + .spawn() + .expect("monero-wallet-rpc failed to stop"); + debug!("{:?}", rpc_output.stdout); + } + if !cm { + let nevmes_output = std::process::Command::new("pkill") + .arg("nevmes") + .spawn() + .expect("nevmes failed to stop"); + debug!("{:?}", nevmes_output.stdout); + } + let i2pz_output = std::process::Command::new("pkill") + .arg("i2p-zero") + .spawn() + .expect("i2p-zero failed to stop"); + debug!("{:?}", i2pz_output.stdout); +} + +/// Move temp files to /tmp +pub fn stage_cleanup(f: String) { + info!("staging {} for cleanup", &f); + let output = std::process::Command::new("mv") + .args([&f, "/tmp"]) + .spawn() + .expect("cleanup staging failed"); + debug!("{:?}", output.stdout); +} diff --git a/nevmes-gui/.gitignore b/nevmes-gui/.gitignore new file mode 100755 index 0000000..901b6ab --- /dev/null +++ b/nevmes-gui/.gitignore @@ -0,0 +1,4 @@ +/target +/core +notes.txt +.env \ No newline at end of file diff --git a/nevmes-gui/Cargo.lock b/nevmes-gui/Cargo.lock new file mode 100644 index 0000000..789e385 --- /dev/null +++ b/nevmes-gui/Cargo.lock @@ -0,0 +1,4752 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ab_glyph" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5110f1c78cf582855d895ecd0746b653db010cec6d9f5575293f27934d980a39" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" + +[[package]] +name = "accesskit" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4803cf8c252f374ae6bfbb341e49e5a37f7601f2ce74a105927a663eba952c67" +dependencies = [ + "enumn", + "serde", +] + +[[package]] +name = "addr2line" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "adler32" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if", + "once_cell", + "serde", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" +dependencies = [ + "memchr", +] + +[[package]] +name = "android-activity" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c77a0045eda8b888c76ea473c2b0515ba6f471d318f8927c5c72240937035a6" +dependencies = [ + "android-properties", + "bitflags 1.3.2", + "cc", + "jni-sys", + "libc", + "log 0.4.17", + "ndk", + "ndk-context", + "ndk-sys", + "num_enum", +] + +[[package]] +name = "android-properties" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6342bd4f5a1205d7f41e94a41a901f5647c938cdfa96036338e8533c9d6c2450" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is-terminal", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" + +[[package]] +name = "anstyle-parse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "anstyle-wincon" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +dependencies = [ + "anstyle", + "windows-sys 0.48.0", +] + +[[package]] +name = "arboard" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6041616acea41d67c4a984709ddab1587fd0b10efe5cc563fee954d2f011854" +dependencies = [ + "clipboard-win", + "log 0.4.17", + "objc", + "objc-foundation", + "objc_id", + "once_cell", + "parking_lot", + "thiserror", + "winapi", + "x11rb", +] + +[[package]] +name = "arrayref" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" + +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + +[[package]] +name = "ash" +version = "0.37.2+1.3.238" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28bf19c1f0a470be5fbf7522a308a05df06610252c5bcf5143e1b23f629a9a03" +dependencies = [ + "libloading", +] + +[[package]] +name = "async-stream" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "async-trait" +version = "0.1.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "atomic" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b88d82667eca772c4aa12f0f1348b3ae643424c8876448f3f7bd5787032e234c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "atomic_refcell" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79d6dc922a2792b006573f60b2648076355daeae5ce9cb59507e5908c9625d31" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide 0.6.2", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" + +[[package]] +name = "binascii" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72" + +[[package]] +name = "bindgen" +version = "0.65.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfdf7b466f9a4903edc73f95d6d2bcd5baf8ae620638762244d3f60143643cc5" +dependencies = [ + "bitflags 1.3.2", + "cexpr", + "clang-sys", + "lazy_static 1.4.0", + "lazycell", + "log 0.4.17", + "peeking_take_while", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.15", + "which", +] + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24a6904aef64d73cf10ab17ebace7befb918b82164785cb89907993be7f83813" + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-sys" +version = "0.1.0-beta.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa55741ee90902547802152aaf3f8e5248aab7e21468089560d4c8840561146" +dependencies = [ + "objc-sys", +] + +[[package]] +name = "block2" +version = "0.2.0-alpha.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dd9e63c1744f755c2f60332b88de39d341e5e86239014ad839bd71c106dec42" +dependencies = [ + "block-sys", + "objc2-encode", +] + +[[package]] +name = "build-rs" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b00b8763668c99f8d9101b8a0dd82106f58265464531a79b2cef0d9a30c17dd2" + +[[package]] +name = "bumpalo" +version = "3.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b1ce199063694f33ffb7dd4e0ee620741495c32833cde5aa08f02a0bf96f0c8" + +[[package]] +name = "bytemuck" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdde5c9cd29ebd706ce1b35600920a33550e402fc998a2e53ad3b42c3c47a192" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "calloop" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a59225be45a478d772ce015d9743e49e92798ece9e34eda9a6aa2a6a7f40192" +dependencies = [ + "log 0.4.17", + "nix 0.25.1", + "slotmap", + "thiserror", + "vec_map", +] + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +dependencies = [ + "jobserver", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-expr" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8790cf1286da485c72cf5fc7aeba308438800036ec67d89425924c4807268c9" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "cgl" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" +dependencies = [ + "libc", +] + +[[package]] +name = "checked_int_cast" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17cc5e6b5ab06331c33589842070416baa137e8b0eb912b008cfd4a78ada7919" + +[[package]] +name = "chrono" +version = "0.4.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-integer", + "num-traits", + "time 0.1.45", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "clang-sys" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "4.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a1f23fa97e1d1641371b51f35535cb26959b8e27ab50d167a8b996b5bada819" +dependencies = [ + "clap_builder", + "clap_derive", + "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fdc5d93c358224b4d6867ef1356d740de2303e9892edc06c5340daeccd96bab" +dependencies = [ + "anstream", + "anstyle", + "bitflags 1.3.2", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "clap_lex" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1" + +[[package]] +name = "clipboard-win" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362" +dependencies = [ + "error-code", + "str-buf", + "winapi", +] + +[[package]] +name = "cocoa-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "931d3837c286f56e3c58423ce4eba12d08db2374461a785c86f672b08b5650d6" +dependencies = [ + "bitflags 1.3.2", + "block", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "com-rs" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf43edc576402991846b093a7ca18a3477e0ef9c588cde84964b5d3e43016642" + +[[package]] +name = "combine" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "conv" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ff10625fd0ac447827aa30ea8b861fead473bb60aeb73af6c1c58caf0d1299" +dependencies = [ + "custom_derive", +] + +[[package]] +name = "cookie" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24" +dependencies = [ + "percent-encoding", + "time 0.3.20", + "version_check", +] + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] +name = "core-graphics" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "foreign-types", + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset 0.8.0", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cstr-argument" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bd9c8e659a473bce955ae5c35b116af38af11a7acb0b480e01f3ed348aeb40" +dependencies = [ + "cfg-if", + "memchr", +] + +[[package]] +name = "custom_derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9" + +[[package]] +name = "cxx" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn 2.0.15", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "d3d12" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8f0de2f5a8e7bd4a9eec0e3c781992a4ce1724f68aec7d7a3715344de8b39da" +dependencies = [ + "bitflags 1.3.2", + "libloading", + "winapi", +] + +[[package]] +name = "darling" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +dependencies = [ + "darling_core", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "deflate" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174" +dependencies = [ + "adler32", + "byteorder", +] + +[[package]] +name = "devise" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6eacefd3f541c66fc61433d65e54e0e46e0a029a819a7dbbc7a7b489e8a85f8" +dependencies = [ + "devise_codegen", + "devise_core", +] + +[[package]] +name = "devise_codegen" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8cf4b8dd484ede80fd5c547592c46c3745a617c8af278e2b72bea86b2dfed6" +dependencies = [ + "devise_core", + "quote", +] + +[[package]] +name = "devise_core" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35b50dba0afdca80b187392b24f2499a88c336d5a8493e4b4ccfb608708be56a" +dependencies = [ + "bitflags 2.2.1", + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common", + "subtle", +] + +[[package]] +name = "digest_auth" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa30657988b2ced88f68fe490889e739bf98d342916c33ed3100af1d6f1cbc9c" +dependencies = [ + "digest 0.9.0", + "hex", + "md-5", + "rand", + "sha2 0.9.9", +] + +[[package]] +name = "diqwest" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5169a8000168e1ed2347ddd48be77530e1d976625008e89f9c3e4a35169d46" +dependencies = [ + "async-trait", + "digest_auth", + "reqwest", + "url", +] + +[[package]] +name = "directories-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dlib" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794" +dependencies = [ + "libloading", +] + +[[package]] +name = "downcast-rs" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" + +[[package]] +name = "dyn-clonable" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e9232f0e607a262ceb9bd5141a3dfb3e4db6994b31989bbfd845878cba59fd4" +dependencies = [ + "dyn-clonable-impl", + "dyn-clone", +] + +[[package]] +name = "dyn-clonable-impl" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "558e40ea573c374cf53507fd240b7ee2f5477df7cfebdb97323ec61c719399c5" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "dyn-clone" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" + +[[package]] +name = "ecolor" +version = "0.21.0" +dependencies = [ + "bytemuck", + "serde", +] + +[[package]] +name = "eframe" +version = "0.21.3" +dependencies = [ + "bytemuck", + "directories-next", + "egui", + "egui-wgpu", + "egui-winit", + "egui_glow", + "glow", + "glutin", + "glutin-winit", + "js-sys", + "percent-encoding", + "pollster", + "raw-window-handle", + "ron", + "serde", + "thiserror", + "tracing", + "tts", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu", + "winit", +] + +[[package]] +name = "egui" +version = "0.21.0" +dependencies = [ + "accesskit", + "ahash 0.8.3", + "epaint", + "nohash-hasher", + "ron", + "serde", + "tracing", +] + +[[package]] +name = "egui-wgpu" +version = "0.21.0" +dependencies = [ + "bytemuck", + "epaint", + "tracing", + "type-map", + "wgpu", + "winit", +] + +[[package]] +name = "egui-winit" +version = "0.21.1" +dependencies = [ + "android-activity", + "arboard", + "egui", + "instant", + "serde", + "smithay-clipboard", + "tracing", + "webbrowser", + "winit", +] + +[[package]] +name = "egui_extras" +version = "0.21.0" +dependencies = [ + "egui", + "image", + "serde", +] + +[[package]] +name = "egui_glow" +version = "0.21.0" +dependencies = [ + "bytemuck", + "egui", + "glow", + "memoffset 0.6.5", + "tracing", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + +[[package]] +name = "emath" +version = "0.21.0" +dependencies = [ + "bytemuck", + "serde", +] + +[[package]] +name = "encoding_rs" +version = "0.8.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "enumn" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48016319042fb7c87b78d2993084a831793a897a5cd1a2a67cab9d1eeb4b7d76" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "env_logger" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +dependencies = [ + "humantime", + "is-terminal", + "log 0.4.17", + "regex", + "termcolor", +] + +[[package]] +name = "epaint" +version = "0.21.0" +dependencies = [ + "ab_glyph", + "ahash 0.8.3", + "atomic_refcell", + "bytemuck", + "ecolor", + "emath", + "nohash-hasher", + "parking_lot", + "serde", +] + +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "error-code" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21" +dependencies = [ + "libc", + "str-buf", +] + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "fdeflate" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d329bdeac514ee06249dabc27877490f17f5d371ec693360768b838e19f3ae10" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "figment" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e56602b469b2201400dec66a66aec5a9b8761ee97cd1b8c96ab2483fcc16cc9" +dependencies = [ + "atomic", + "pear", + "serde", + "toml 0.5.11", + "uncased", + "version_check", +] + +[[package]] +name = "flate2" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" +dependencies = [ + "crc32fast", + "miniz_oxide 0.7.1", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-io" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-sink" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" + +[[package]] +name = "futures-task" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" + +[[package]] +name = "futures-util" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "gcc" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" + +[[package]] +name = "generator" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3e123d9ae7c02966b4d892e550bdc32164f05853cd40ab570650ad600596a8a" +dependencies = [ + "cc", + "libc", + "log 0.4.17", + "rustversion", + "windows 0.48.0", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "gethostname" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "getrandom" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "gif" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3edd93c6756b4dfaf2709eafcc345ba2636565295c198a9cfbf75fa5e3e00b06" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "gimli" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4" + +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log 0.4.17", + "xml-rs", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "glow" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e007a07a24de5ecae94160f141029e9a347282cfe25d1d58d85d845cf3130f1" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glutin" +version = "0.30.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f89bab9ec7715de13d5d5402238e66f48e3a5ae636ebb45aba4013c962e2ff15" +dependencies = [ + "bitflags 1.3.2", + "cfg_aliases", + "cgl", + "core-foundation", + "dispatch", + "glutin_egl_sys", + "glutin_glx_sys", + "glutin_wgl_sys", + "libloading", + "objc2", + "once_cell", + "raw-window-handle", + "wayland-sys 0.30.1", + "windows-sys 0.45.0", + "x11-dl", +] + +[[package]] +name = "glutin-winit" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "629a873fc04062830bfe8f97c03773bcd7b371e23bcc465d0a61448cd1588fa4" +dependencies = [ + "cfg_aliases", + "glutin", + "raw-window-handle", + "winit", +] + +[[package]] +name = "glutin_egl_sys" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5aaf0abb5c4148685b33101ae326a207946b4d3764d6cdc79f8316cdaa8367d" +dependencies = [ + "gl_generator", + "windows-sys 0.45.0", +] + +[[package]] +name = "glutin_glx_sys" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b53cb5fe568964aa066a3ba91eac5ecbac869fb0842cd0dc9e412434f1a1494" +dependencies = [ + "gl_generator", + "x11-dl", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef89398e90033fc6bc65e9bd42fd29bbbfd483bda5b56dc5562f455550618165" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "gpg-error" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89aaeddbfb92313378c58e98abadaaa34082b3855f1d455576eeeda08bd592c" +dependencies = [ + "libgpg-error-sys", +] + +[[package]] +name = "gpgme" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57539732fbe58eacdb984734b72b470ed0bca3ab7a49813271878567025ac44f" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "conv", + "cstr-argument", + "gpg-error", + "gpgme-sys", + "libc", + "memoffset 0.7.1", + "once_cell", + "smallvec", + "static_assertions", +] + +[[package]] +name = "gpgme-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "509223d659c06e4a26229437d6ac917723f02d31917c86c6ecd50e8369741cf7" +dependencies = [ + "build-rs", + "libc", + "libgpg-error-sys", + "system-deps", + "winreg", +] + +[[package]] +name = "gpu-alloc" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fc59e5f710e310e76e6707f86c561dd646f69a8876da9131703b2f717de818d" +dependencies = [ + "bitflags 1.3.2", + "gpu-alloc-types", +] + +[[package]] +name = "gpu-alloc-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54804d0d6bc9d7f26db4eaec1ad10def69b599315f487d32c334a80d1efe67a5" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "gpu-allocator" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce95f9e2e11c2c6fadfce42b5af60005db06576f231f5c92550fdded43c423e8" +dependencies = [ + "backtrace", + "log 0.4.17", + "thiserror", + "winapi", + "windows 0.44.0", +] + +[[package]] +name = "gpu-descriptor" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b0c02e1ba0bdb14e965058ca34e09c020f8e507a760df1121728e0aef68d57a" +dependencies = [ + "bitflags 1.3.2", + "gpu-descriptor-types", + "hashbrown", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "363e3677e55ad168fef68cf9de3a4a310b53124c5e784c53a1d70e92d23f2126" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "h2" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17f8a914c2987b688368b5138aa05321db91f4090cf26118185672ad588bce21" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.6", +] + +[[package]] +name = "hassle-rs" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90601c6189668c7345fc53842cb3f3a3d872203d523be1b3cb44a36a3e62fb85" +dependencies = [ + "bitflags 1.3.2", + "com-rs", + "libc", + "libloading", + "thiserror", + "widestring", + "winapi", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.6", +] + +[[package]] +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "hyper" +version = "0.14.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows 0.48.0", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +dependencies = [ + "cxx", + "cxx-build", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "image" +version = "0.23.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "gif", + "jpeg-decoder", + "num-iter", + "num-rational", + "num-traits", + "png 0.16.8", + "scoped_threadpool", + "tiff", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown", + "serde", +] + +[[package]] +name = "inlinable_string" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" +dependencies = [ + "hermit-abi 0.3.1", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "ipnet" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" + +[[package]] +name = "is-terminal" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" +dependencies = [ + "hermit-abi 0.3.1", + "io-lifetimes", + "rustix", + "windows-sys 0.48.0", +] + +[[package]] +name = "itoa" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log 0.4.17", + "thiserror", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +dependencies = [ + "libc", +] + +[[package]] +name = "jpeg-decoder" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2" +dependencies = [ + "rayon", +] + +[[package]] +name = "js-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "jwt" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6204285f77fe7d9784db3fdc449ecce1a0114927a51d5a41c4c7a292011c015f" +dependencies = [ + "base64 0.13.1", + "crypto-common", + "digest 0.10.6", + "hmac", + "serde", + "serde_json", + "sha2 0.10.6", +] + +[[package]] +name = "khronos-egl" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c2352bd1d0bceb871cb9d40f24360c8133c11d7486b68b5381c1dd1a32015e3" +dependencies = [ + "libc", + "libloading", + "pkg-config", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + +[[package]] +name = "lazy_static" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libc" +version = "0.2.142" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317" + +[[package]] +name = "libgpg-error-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c97079310f39c835d3bd73578379d040f779614bb331c7ffbb6630fee6420290" +dependencies = [ + "build-rs", + "system-deps", + "winreg", +] + +[[package]] +name = "liblmdb-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "feed38a3a580f60bf61aaa067b0ff4123395966839adeaf67258a9e50c4d2e49" +dependencies = [ + "gcc", + "libc", +] + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "link-cplusplus" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" +dependencies = [ + "cc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b64f40e5e03e0d54f03845c8197d0291253cdbedfb1cb46b13c2c117554a9f4c" + +[[package]] +name = "lmdb-rs" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aefe7b433f795629ce42f35ccf7a620c38bd457238bfaa2489dafc7e36167e7" +dependencies = [ + "bitflags 0.7.0", + "libc", + "liblmdb-sys", + "log 0.3.9", +] + +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" +dependencies = [ + "log 0.4.17", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "loom" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "serde", + "serde_json", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "md-5" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5a279bb9607f9f53c22d496eade00d138d1bdcccd07d74650387cf94942a15" +dependencies = [ + "block-buffer 0.9.0", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memmap2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memoffset" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" +dependencies = [ + "autocfg", +] + +[[package]] +name = "metal" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de11355d1f6781482d027a3b4d4de7825dcedb197bf573e0596d00008402d060" +dependencies = [ + "bitflags 1.3.2", + "block", + "core-graphics-types", + "foreign-types", + "log 0.4.17", + "objc", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" +dependencies = [ + "adler32", +] + +[[package]] +name = "miniz_oxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +dependencies = [ + "adler", + "autocfg", +] + +[[package]] +name = "miniz_oxide" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +dependencies = [ + "adler", +] + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +dependencies = [ + "libc", + "log 0.4.17", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.45.0", +] + +[[package]] +name = "multer" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2" +dependencies = [ + "bytes", + "encoding_rs", + "futures-util", + "http", + "httparse", + "log 0.4.17", + "memchr", + "mime", + "spin", + "tokio", + "tokio-util", + "version_check", +] + +[[package]] +name = "naga" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eafe22a23b797c9bc227c6c896419b26b5bb88fa903417a3adaed08778850d5" +dependencies = [ + "bit-set", + "bitflags 1.3.2", + "codespan-reporting", + "hexf-parse", + "indexmap", + "log 0.4.17", + "num-traits", + "rustc-hash", + "spirv", + "termcolor", + "thiserror", + "unicode-xid", +] + +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static 1.4.0", + "libc", + "log 0.4.17", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "ndk" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "451422b7e4718271c8b5b3aadf5adedba43dc76312454b387e98fae0fc951aa0" +dependencies = [ + "bitflags 1.3.2", + "jni-sys", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-glue" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0434fabdd2c15e0aab768ca31d5b7b333717f03cf02037d5a0a3ff3c278ed67f" +dependencies = [ + "libc", + "log 0.4.17", + "ndk", + "ndk-context", + "ndk-macro", + "ndk-sys", + "once_cell", + "parking_lot", +] + +[[package]] +name = "ndk-macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0df7ac00c4672f9d5aece54ee3347520b7e20f158656c7db2e6de01902eb7a6c" +dependencies = [ + "darling", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ndk-sys" +version = "0.4.1+23.1.7779620" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cf2aae958bd232cac5069850591667ad422d263686d75b52a065f9badeee5a3" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "nevmes_core" +version = "0.1.0-alpha" +dependencies = [ + "chrono", + "clap", + "diqwest", + "env_logger", + "gpgme", + "hex", + "hmac", + "jwt", + "lmdb-rs", + "log 0.4.17", + "rand", + "rand_core", + "reqwest", + "rocket", + "schedule_recv", + "serde", + "serde_json", + "sha2 0.10.6", + "tokio", +] + +[[package]] +name = "nevmes_gui" +version = "0.1.0-alpha" +dependencies = [ + "bytemuck", + "chrono", + "console_error_panic_hook", + "eframe", + "egui", + "egui_extras", + "hex", + "image", + "log 0.4.17", + "nevmes_core", + "poll-promise", + "qrcode", + "reqwest", + "serde", + "sha2 0.10.6", + "tokio", + "tracing", + "tracing-subscriber", + "tracing-wasm", + "wasm-bindgen-futures", +] + +[[package]] +name = "nix" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset 0.6.5", +] + +[[package]] +name = "nix" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset 0.6.5", +] + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +dependencies = [ + "hermit-abi 0.2.6", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", + "objc_exception", +] + +[[package]] +name = "objc-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + +[[package]] +name = "objc-sys" +version = "0.2.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b9834c1e95694a05a828b59f55fa2afec6288359cda67146126b3f90a55d7" + +[[package]] +name = "objc2" +version = "0.3.0-beta.3.patch-leaks.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e01640f9f2cb1220bbe80325e179e532cb3379ebcd1bf2279d703c19fe3a468" +dependencies = [ + "block2", + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2-encode" +version = "2.0.0-pre.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abfcac41015b00a120608fdaa6938c44cb983fee294351cc4bac7638b4e50512" +dependencies = [ + "objc-sys", +] + +[[package]] +name = "objc_exception" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +dependencies = [ + "cc", +] + +[[package]] +name = "objc_id" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] + +[[package]] +name = "object" +version = "0.30.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "openssl" +version = "0.10.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01b8574602df80f7b85fdfc5392fa884a4e3b3f4f35402c070ab34c3d3f78d56" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e17f59264b2809d77ae94f0e1ebabc434773f370d6ca667bd223ea10e06cc7e" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "orbclient" +version = "0.3.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "221d488cd70617f1bd599ed8ceb659df2147d9393717954d82a0f5e8032a6ab1" +dependencies = [ + "redox_syscall 0.3.5", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "owned_ttf_parser" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "706de7e2214113d63a8238d1910463cfce781129a6f263d13fdb09ff64355ba4" +dependencies = [ + "ttf-parser", +] + +[[package]] +name = "oxilangtag" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d91edf4fbb970279443471345a4e8c491bf05bb283b3e6c88e4e606fd8c181b" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "windows-sys 0.45.0", +] + +[[package]] +name = "pear" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec95680a7087503575284e5063e14b694b7a9c0b065e5dceec661e0497127e8" +dependencies = [ + "inlinable_string", + "pear_codegen", + "yansi", +] + +[[package]] +name = "pear_codegen" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9661a3a53f93f09f2ea882018e4d7c88f6ff2956d809a276060476fd8c879d3c" +dependencies = [ + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" + +[[package]] +name = "png" +version = "0.16.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "deflate", + "miniz_oxide 0.3.7", +] + +[[package]] +name = "png" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaeebc51f9e7d2c150d3f3bfeb667f2aa985db5ef1e3d212847bdedb488beeaa" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide 0.7.1", +] + +[[package]] +name = "poll-promise" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcf2a02372dfae23c9c01267fb296b8a3413bb4e45fbd589c3ac73c6dcfbb305" +dependencies = [ + "static_assertions", +] + +[[package]] +name = "pollster" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "prettyplease" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ceca8aaf45b5c46ec7ed39fff75f57290368c1846d33d24a122ca81416ab058" +dependencies = [ + "proc-macro2", + "syn 2.0.15", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "606c4ba35817e2922a308af55ad51bab3645b59eae5c570d4a6cf07e36bd493b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", + "version_check", + "yansi", +] + +[[package]] +name = "profiling" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "332cd62e95873ea4f41f3dfd6bbbfc5b52aec892d7e8d534197c4720a0bbbab2" + +[[package]] +name = "qrcode" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d2f1455f3630c6e5107b4f2b94e74d76dea80736de0981fd27644216cff57f" +dependencies = [ + "checked_int_cast", + "image", +] + +[[package]] +name = "quote" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "range-alloc" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8a99fddc9f0ba0a85884b8d14e3592853e787d581ca1816c91349b10e4eeab" + +[[package]] +name = "raw-window-handle" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" + +[[package]] +name = "rayon" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "num_cpus", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall 0.2.16", + "thiserror", +] + +[[package]] +name = "ref-cast" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43faa91b1c8b36841ee70e97188a869d37ae21759da6846d4be66de5bf7b12c" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d2275aab483050ab2a7364c1a46604865ee7d6906684e08db0f090acf74f9e7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "regex" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.7.1", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" + +[[package]] +name = "renderdoc-sys" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1382d1f0a252c4bf97dc20d979a2fdd05b024acd7c2ed0f7595d7817666a157" + +[[package]] +name = "reqwest" +version = "0.11.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b71749df584b7f4cac2c426c127a7c785a5106cc98f7a8feb044115f0fa254" +dependencies = [ + "base64 0.21.0", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log 0.4.17", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + +[[package]] +name = "rocket" +version = "0.5.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58734f7401ae5cfd129685b48f61182331745b357b96f2367f01aebaf1cc9cc9" +dependencies = [ + "async-stream", + "async-trait", + "atomic", + "binascii", + "bytes", + "either", + "figment", + "futures", + "indexmap", + "is-terminal", + "log 0.4.17", + "memchr", + "multer", + "num_cpus", + "parking_lot", + "pin-project-lite", + "rand", + "ref-cast", + "rocket_codegen", + "rocket_http", + "serde", + "serde_json", + "state", + "tempfile", + "time 0.3.20", + "tokio", + "tokio-stream", + "tokio-util", + "ubyte", + "version_check", + "yansi", +] + +[[package]] +name = "rocket_codegen" +version = "0.5.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7093353f14228c744982e409259fb54878ba9563d08214f2d880d59ff2fc508b" +dependencies = [ + "devise", + "glob", + "indexmap", + "proc-macro2", + "quote", + "rocket_http", + "syn 2.0.15", + "unicode-xid", +] + +[[package]] +name = "rocket_http" +version = "0.5.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936012c99162a03a67f37f9836d5f938f662e26f2717809761a9ac46432090f4" +dependencies = [ + "cookie", + "either", + "futures", + "http", + "hyper", + "indexmap", + "log 0.4.17", + "memchr", + "pear", + "percent-encoding", + "pin-project-lite", + "ref-cast", + "serde", + "smallvec", + "stable-pattern", + "state", + "time 0.3.20", + "tokio", + "uncased", +] + +[[package]] +name = "ron" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300a51053b1cb55c80b7a9fde4120726ddf25ca241a1cbb926626f62fb136bff" +dependencies = [ + "base64 0.13.1", + "bitflags 1.3.2", + "serde", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustix" +version = "0.37.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0661814f891c57c930a610266415528da53c4933e6dea5fb350cbfe048a9ece" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustversion" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" + +[[package]] +name = "ryu" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +dependencies = [ + "windows-sys 0.42.0", +] + +[[package]] +name = "schedule_recv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca1520cf9d3182329ceb57b9a6b37eb68fe94f5d46c0be4aa2d2a522ec3d40eb" +dependencies = [ + "lazy_static 0.2.11", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scoped_threadpool" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "scratch" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" + +[[package]] +name = "sctk-adwaita" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda4e97be1fd174ccc2aae81c8b694e803fa99b34e8fd0f057a9d70698e3ed09" +dependencies = [ + "ab_glyph", + "log 0.4.17", + "memmap2", + "smithay-client-toolkit", + "tiny-skia", +] + +[[package]] +name = "security-framework" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.160" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.160" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "serde_json" +version = "1.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.6", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static 1.4.0", +] + +[[package]] +name = "shlex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "238abfbb77c1915110ad968465608b68e869e0772622c9656714e73e5a1a522f" + +[[package]] +name = "slab" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg", +] + +[[package]] +name = "slotmap" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "smithay-client-toolkit" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f307c47d32d2715eb2e0ece5589057820e0e5e70d07c247d1063e844e107f454" +dependencies = [ + "bitflags 1.3.2", + "calloop", + "dlib", + "lazy_static 1.4.0", + "log 0.4.17", + "memmap2", + "nix 0.24.3", + "pkg-config", + "wayland-client", + "wayland-cursor", + "wayland-protocols", +] + +[[package]] +name = "smithay-clipboard" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a345c870a1fae0b1b779085e81b51e614767c239e93503588e54c5b17f4b0e8" +dependencies = [ + "smithay-client-toolkit", + "wayland-client", +] + +[[package]] +name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "speech-dispatcher" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5727d53c474ba5ada07784ad7d203cf896a74854cfee0eb32376b00759eb2972" +dependencies = [ + "lazy_static 1.4.0", + "libc", + "speech-dispatcher-sys", +] + +[[package]] +name = "speech-dispatcher-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c3e8acdf2b1f4bb13f1813b40b52f3edf4cc94d8a55fe713a584f672a10388d" +dependencies = [ + "bindgen", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spirv" +version = "0.2.0+1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246bfa38fe3db3f1dfc8ca5a2cdeb7348c78be2112740cc0ec8ef18b6d94f830" +dependencies = [ + "bitflags 1.3.2", + "num-traits", +] + +[[package]] +name = "stable-pattern" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4564168c00635f88eaed410d5efa8131afa8d8699a612c80c455a0ba05c21045" +dependencies = [ + "memchr", +] + +[[package]] +name = "state" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbe866e1e51e8260c9eed836a042a5e7f6726bb2b411dffeaa712e19c388f23b" +dependencies = [ + "loom", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "str-buf" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0" + +[[package]] +name = "strict-num" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9df65f20698aeed245efdde3628a6b559ea1239bbb871af1b6e3b58c413b2bd1" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "system-deps" +version = "6.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0fe581ad25d11420b873cf9aedaca0419c2b411487b134d4d21065f3d092055" +dependencies = [ + "cfg-expr", + "heck", + "pkg-config", + "toml 0.7.3", + "version-compare", +] + +[[package]] +name = "target-lexicon" +version = "0.12.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd1ba337640d60c3e96bc6f0638a939b9c9a7f2c316a1598c279828b3d1dc8c5" + +[[package]] +name = "tempfile" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall 0.3.5", + "rustix", + "windows-sys 0.45.0", +] + +[[package]] +name = "termcolor" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tiff" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a53f4706d65497df0c4349241deddf35f84cee19c87ed86ea8ca590f4464437" +dependencies = [ + "jpeg-decoder", + "miniz_oxide 0.4.4", + "weezl", +] + +[[package]] +name = "time" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + +[[package]] +name = "time" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" +dependencies = [ + "itoa", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" + +[[package]] +name = "time-macros" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" +dependencies = [ + "time-core", +] + +[[package]] +name = "tiny-skia" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8493a203431061e901613751931f047d1971337153f96d0e5e363d6dbf6a67" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "png 0.17.8", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adbfb5d3f3dd57a0e11d12f4f13d4ebbbc1b5c15b7ab0a156d030b21da5f677c" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c786bf8134e5a3a166db9b29ab8f48134739014a3eca7bc6bfa95d673b136f" +dependencies = [ + "autocfg", + "bytes", + "libc", + "mio", + "num_cpus", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-macros" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "toml" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b403acf6f2bb0859c93c7f0d967cb4a75a7ac552100f9322faf64dc047669b21" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9cf6a813d3f40c88b0b6b6f29a5c95c6cdbf97c1f9cc53fb820200f5ad814d" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static 1.4.0", + "log 0.4.17", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "tracing-wasm" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4575c663a174420fa2d78f4108ff68f65bf2fbb7dd89f33749b6e826b3626e07" +dependencies = [ + "tracing", + "tracing-subscriber", + "wasm-bindgen", +] + +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + +[[package]] +name = "ttf-parser" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44dcf002ae3b32cd25400d6df128c5babec3927cd1eb7ce813cfff20eb6c3746" + +[[package]] +name = "tts" +version = "0.25.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e339627916d1e1425f806c68b57d7eb6f9486ef0500829b1324556bef3b4fa2d" +dependencies = [ + "cocoa-foundation", + "core-foundation", + "dyn-clonable", + "jni", + "lazy_static 1.4.0", + "libc", + "log 0.4.17", + "ndk-context", + "ndk-glue", + "objc", + "oxilangtag", + "speech-dispatcher", + "thiserror", + "wasm-bindgen", + "web-sys", + "windows 0.48.0", +] + +[[package]] +name = "type-map" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb68604048ff8fa93347f02441e4487594adc20bb8a084f9e564d2b827a0a9f" +dependencies = [ + "rustc-hash", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "ubyte" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c81f0dae7d286ad0d9366d7679a77934cfc3cf3a8d67e82669794412b2368fe6" +dependencies = [ + "serde", +] + +[[package]] +name = "uncased" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b01702b0fd0b3fadcf98e098780badda8742d4f4a7676615cad90e8ac73622" +dependencies = [ + "serde", + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + +[[package]] +name = "unicode-ident" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "url" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version-compare" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "walkdir" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log 0.4.17", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +dependencies = [ + "bumpalo", + "log 0.4.17", + "once_cell", + "proc-macro2", + "quote", + "syn 1.0.109", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" + +[[package]] +name = "wayland-client" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f3b068c05a039c9f755f881dc50f01732214f5685e379829759088967c46715" +dependencies = [ + "bitflags 1.3.2", + "downcast-rs", + "libc", + "nix 0.24.3", + "scoped-tls", + "wayland-commons", + "wayland-scanner", + "wayland-sys 0.29.5", +] + +[[package]] +name = "wayland-commons" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8691f134d584a33a6606d9d717b95c4fa20065605f798a3f350d78dced02a902" +dependencies = [ + "nix 0.24.3", + "once_cell", + "smallvec", + "wayland-sys 0.29.5", +] + +[[package]] +name = "wayland-cursor" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661" +dependencies = [ + "nix 0.24.3", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b950621f9354b322ee817a23474e479b34be96c2e909c14f7bc0100e9a970bc6" +dependencies = [ + "bitflags 1.3.2", + "wayland-client", + "wayland-commons", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f4303d8fa22ab852f789e75a967f0a2cdc430a607751c0499bada3e451cbd53" +dependencies = [ + "proc-macro2", + "quote", + "xml-rs", +] + +[[package]] +name = "wayland-sys" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be12ce1a3c39ec7dba25594b97b42cb3195d54953ddb9d3d95a7c3902bc6e9d4" +dependencies = [ + "dlib", + "lazy_static 1.4.0", + "pkg-config", +] + +[[package]] +name = "wayland-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b2a02ac608e07132978689a6f9bf4214949c85998c247abadd4f4129b1aa06" +dependencies = [ + "dlib", + "lazy_static 1.4.0", + "log 0.4.17", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webbrowser" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b692165700260bbd40fbc5ff23766c03e339fbaca907aeea5cb77bf0a553ca83" +dependencies = [ + "core-foundation", + "dirs", + "jni", + "log 0.4.17", + "ndk-context", + "objc", + "raw-window-handle", + "url", + "web-sys", +] + +[[package]] +name = "weezl" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" + +[[package]] +name = "wgpu" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d745a1b6d91d85c33defbb29f0eee0450e1d2614d987e14bf6baf26009d132d7" +dependencies = [ + "arrayvec", + "cfg-if", + "js-sys", + "log 0.4.17", + "naga", + "parking_lot", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7131408d940e335792645a98f03639573b0480e9e2e7cddbbab74f7c6d9f3fff" +dependencies = [ + "arrayvec", + "bit-vec", + "bitflags 1.3.2", + "codespan-reporting", + "fxhash", + "log 0.4.17", + "naga", + "parking_lot", + "profiling", + "raw-window-handle", + "smallvec", + "thiserror", + "web-sys", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-hal" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdcf61a283adc744bb5453dd88ea91f3f86d5ca6b027661c6c73c7734ae0288b" +dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bit-set", + "bitflags 1.3.2", + "block", + "core-graphics-types", + "d3d12", + "foreign-types", + "fxhash", + "glow", + "gpu-alloc", + "gpu-allocator", + "gpu-descriptor", + "hassle-rs", + "js-sys", + "khronos-egl", + "libc", + "libloading", + "log 0.4.17", + "metal", + "naga", + "objc", + "parking_lot", + "profiling", + "range-alloc", + "raw-window-handle", + "renderdoc-sys", + "smallvec", + "thiserror", + "wasm-bindgen", + "web-sys", + "wgpu-types", + "winapi", +] + +[[package]] +name = "wgpu-types" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32444e121b0bd00cb02c0de32fde457a9491bd44e03e7a5db6df9b1da2f6f110" +dependencies = [ + "bitflags 1.3.2", + "js-sys", + "web-sys", +] + +[[package]] +name = "which" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +dependencies = [ + "either", + "libc", + "once_cell", +] + +[[package]] +name = "widestring" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17882f045410753661207383517a6f62ec3dbeb6a4ed2acce01f0728238d1983" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-wsapoll" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c17110f57155602a80dca10be03852116403c9ff3cd25b079d666f2aa3df6e" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets 0.48.0", +] + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.0", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "winit" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f504e8c117b9015f618774f8d58cd4781f5a479bc41079c064f974cbb253874" +dependencies = [ + "android-activity", + "bitflags 1.3.2", + "cfg_aliases", + "core-foundation", + "core-graphics", + "dispatch", + "instant", + "libc", + "log 0.4.17", + "mio", + "ndk", + "objc2", + "once_cell", + "orbclient", + "percent-encoding", + "raw-window-handle", + "redox_syscall 0.3.5", + "sctk-adwaita", + "smithay-client-toolkit", + "wasm-bindgen", + "wayland-client", + "wayland-commons", + "wayland-protocols", + "wayland-scanner", + "web-sys", + "windows-sys 0.45.0", + "x11-dl", +] + +[[package]] +name = "winnow" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "x11rb" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "592b4883219f345e712b3209c62654ebda0bb50887f330cbd018d0f654bfd507" +dependencies = [ + "gethostname", + "nix 0.24.3", + "winapi", + "winapi-wsapoll", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56b245751c0ac9db0e006dc812031482784e434630205a93c73cfefcaabeac67" +dependencies = [ + "nix 0.24.3", +] + +[[package]] +name = "xcursor" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "463705a63313cd4301184381c5e8042f0a7e9b4bb63653f216311d4ae74690b7" +dependencies = [ + "nom", +] + +[[package]] +name = "xml-rs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" + +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" diff --git a/nevmes-gui/Cargo.toml b/nevmes-gui/Cargo.toml new file mode 100644 index 0000000..1455358 --- /dev/null +++ b/nevmes-gui/Cargo.toml @@ -0,0 +1,60 @@ +[package] +name = "nevmes_gui" +version = "0.1.0-alpha" +authors = ["emilk", "creating2morrow "] +license = "MIT OR Apache-2.0" +edition = "2021" +rust-version = "1.65" +publish = false +default-run = "nevmes_gui" + +[package.metadata.docs.rs] +all-features = true + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +default = ["glow", "persistence"] + +persistence = ["eframe/persistence", "egui/persistence", "serde"] +web_screen_reader = ["eframe/web_screen_reader"] # experimental +serde = ["dep:serde", "egui/serde"] + +glow = ["eframe/glow"] +wgpu = ["eframe/wgpu", "bytemuck"] + + +[dependencies] +chrono = { version = "0.4", features = ["js-sys", "wasmbind"] } +eframe = { version = "0.21.0", path = "./crates/eframe", default-features = false } +egui = { version = "0.21.0", path = "./crates/egui", features = [ + "extra_debug_asserts", +] } +hex = "0.4.3" +nevmes_core = { path = "../nevmes-core" } +log = "0.4" +qrcode = "0.12" +image = "0.23.14" +reqwest = { version = "0.11", features = ["json"] } +sha2 = "0.10.6" +tokio = { version = "1", features = ["net", "rt-multi-thread"] } +tracing = "0.1" +tracing-subscriber = "0.3" + +# Optional dependencies: + +bytemuck = { version = "1.7.1", optional = true } +egui_extras = { version = "0.21.0", path = "./crates/egui_extras" } +poll-promise = { version = "0.2", optional = true, default-features = false } +serde = { version = "1", optional = true, features = ["derive"] } + +# native: +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +tracing-subscriber = "0.3" + +# web: +[target.'cfg(target_arch = "wasm32")'.dependencies] +console_error_panic_hook = "0.1.6" +tracing-wasm = "0.2" +wasm-bindgen-futures = "0.4" diff --git a/nevmes-gui/LICENSE-APACHE b/nevmes-gui/LICENSE-APACHE new file mode 100644 index 0000000..11069ed --- /dev/null +++ b/nevmes-gui/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/nevmes-gui/LICENSE-MIT b/nevmes-gui/LICENSE-MIT new file mode 100644 index 0000000..c5f2bb1 --- /dev/null +++ b/nevmes-gui/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2018-2021 creating2morrow, Emil Ernerfeldt + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/nevmes-gui/README.md b/nevmes-gui/README.md new file mode 100644 index 0000000..4f6448e --- /dev/null +++ b/nevmes-gui/README.md @@ -0,0 +1,4 @@ + +## Dev + +`cd ../ && cargo build && cd nevmes-gui && RUST_LOG=debug cargo run` diff --git a/nevmes-gui/assets/i2p.png b/nevmes-gui/assets/i2p.png new file mode 100644 index 0000000000000000000000000000000000000000..2fe7f4b9ecb9c9dd6164be5ae0b49b8bcc53657d GIT binary patch literal 10537 zcmeHtcT`i&w>F`KCISkG2nZ;kLP+SLS4DbnQbP(ov=Bn?2nZ|IvO?_5)u+R_1mfj z#53;nLj@!rr4SOs#8aoAkr~DS=ELRg>4tK2L2_Yy-H}{KoFj^8_|a9*;%pb!nS%u; z8Pb>6ulD9!@UH@8j+K*4keR$$cN3}ACl9u@>H#;haE=Bq#$0TXPtY+4wV6i5Cto$0 zT9Mt6>knSoGFofIAE>ow0XdtH{oS|MxJzsD*pIi6s+?>Ra5Avm>t&Cn)ndDP3 zE0+yD5AS<&Sve`{ad)@AI|#a0W#;YfG4{U0n1NhD(b6i5$?EaT6W&M`VNUklx#`y4 zX&FU_Y>hG*_@}7+96pV($X4qyr|R#Z5hw0UV927LmDv_8uNE6 zXY8EkJXY!jCu(wk0J2KW>xTj-Hp5Rk*3o61Um`M@bVxcbOdhiWl$#Z6(33O0b>^8$q{blF9bnLUWAk z>&N7EE{B})PH6(1JsbLt2U+3U(%f8AAN;m<{{Y}==~;A}H9dt`b~s03d6{WzNyFV- zgm>{|S%w`ke(LA7C8J9V{#a0lT__|L)<1xrrr${2tJM>)~ZYoTXp`q?enwCmeYb zi$r6t{2c-T|I^>y+tcM&ItVxz>4J16ntBm?75+z;YUzI<_CG8! zj;O!L`bTW1nqTSsJrJV%pSb_9{v-Ea#zZS^ZE00Exc6yz>Z)=)r~OMK+~AG~>0g(U za5xGf2|*Wa&vQ$<2en9 z>(uj4wKj1&Az&DoDhz`pfW5oxj0YY?#s`J25P!V%^BKUtqn9xmD6 zhWxgp7qNfeU!vb;lp)gNx7Ke>7sp>yiHqykq>zTee@np&hD9QN#Yx2aO$B#=q3w~x z)#LYa{X_2f-;{y`3MBy(LD+#r?cgwwn4}njSXUB~#F7#h6Cu`ugs>g*pXgq0D2xxx z6RBiR z`^(^Oiwx24HyLs9BCdttKNrK_IXhi;{tsWjkH!CC4@BsHJNb|J{gH3ct_>YYL zt*(FR`i~g+kBtAVuKzWX+S5-2?eO%4$j z^PHv<&TEBnBCr4Jp$qVBk$J_`@X7)Ra-C#3^cj8ir$>HN%XWH<^@Ysi(N#S|vG}kQ z`B!yji=F#lw@vt<^rJ=l93VHf*PUd{>&Td07D@2rBi*0Rj5NYD(mA-FRm8nz3t(9! zV+UXW&!b;?Z}tXgZL!#%uVmZw|L!+Tm8S7gZ)W__Kz-Sf;WPBsF|^bj4&h2}xq6vU z4J^}m%KHXUCv_b&@p@&JS+i7yAwYwo{A7itcZ09rF0+!eCE%_ zHgd@`FTY>uM;57cuR<@GJ@qjV*HR~Kyud-rHoR0I_|fA5w{yRfp>fR1I$GXf{*5bn zGfG@Ox>;2F<>8l{N(`r^$1|=mR*0fir`F@ZyCpi<K`6IFOlKEl80|ow6WeFComZ0@h{4RCCO|EEwB<~LZ=>m z{Fd<&cC+%XAHj6p-P{sQ-7a ziIClp51})?WC*{*^%dNcoVP*M=Nj++n`hN-8yDJg+0N66;{0`j)-KL%RBotvPkZ4|{^{ z0V~27ub9Z{CkQMypCLNw$0&&SADW zZ$HlGJ?F8k-pg(k+U6%TP>0$g*0(B@vP;A$zb^xP!p>YW7Y z%T&utPnZ^&2%Wh8F@gJmL1mIMu8OWFk(#NX?YndxB>fp|Y|e(y)@{l0Pdr-XNrJ+0 zE-i7o8+*uZXg zNz#5wPK6D67;;(bC2O49%YzxqA&R|-w^1=rWuHBUGY|Sumu{SkS?_-;0tJXM{#@d# zFKWx@ep4*y)*|pI_fkh9EwfWvcv^P}%Uwmo6QivlmDZ4og?UE?2n72<>imi{O^5ct zz&vR^)!qfd`WMVeHD=EL=fg`^XvO9?ce&dF>BmxwE-9$WCYWXQc5aYYw!~F;nL+Rm zk=sDQ8%oJ|fZ9Tph|T>LxAh&GPr`73yHkRf$A^IU;myPdZ&J@`Ni@J;ckqQw$EVtX zp$u6;=(=>!RcaHa`Q@~^+;-YKRbtul?5coEZ?>-dyrSmbo)I*yXcJq{)!4A`fzBf6 zHn8BQ#v7-t@f)DDWGR;gf}Osh;rUkwpMk;I@X0!oxAq(*^wb^07xCs&Y#9Je1Z=r? zEG&DMA20(<=&$OUpM5kjIyAnv)+muK`>w$D4RvFay{$fvVfvBT9rqK2c5v4kS4Oax zhwpmmns8xb^7gAWxXiRd`Z>Nc>k-?K#*xrX{B2JKbqf@VmZj{D(`A`3=cK-5m;$MM z68!E9lJ?Ezw`?zy-J5E6Y&D0_J-6JRYt%eGaf zH@i_kWi(8vlP&Ea`+ju|i*nYq%+|+K@!QK>_(2Y&h8aiG=!G46qqL*6(Pq{&`&HaH zTajl&VtB$#%fKx1m*rik&&KO=1<{=0 zmsBqSYy*9`P#+|Vj$lJ?OuE(*?YD-DF!OMwjlRJB8|NEp6i=wn3>4C7hFEl<1pD<1 z1EUiGLDYu}!$m#bku08Oj9Jf@(q7>%0kO31I-Cc+H&Ihj@LPdR9$u7gMSyj(cCnS6 z{hpqbZYJzF!<0fwso3{im8saT>{;}o6nIyhocYXXQqJ$x`o^h2=S_A61VT1Ss9* zZ?F-0&&*1@ufwUI3&MXtr*PAcH(SUh)F97)`g?o*NrWop_GCzz>V+>EkWY{m@hoeK z530&#nrT7;6W`iY&IfqTgieb%9ZB?fe+g=9rrxCL;mEeINNZ{IM2-iE*F`>>U%gd} zo${NaDg~sfr481e9p^ffUYKA44t|hKKF)By%iAE4E-kOJTo36XYqXxhT@b#wmpjzi zBWYUhvcOt{qCRL_WtgT;PY+EO#2Ad-ozObkns@Pk|Fny73Y?Bneu1x#eq7sVdAy|< zPB=?{pQlV)5~RSi&RP zx#%hE(_JVBHAfWT+=z@Q`XPO^YJx7S(~U z`<;X@Xkj=o;c})?du@Kc7Fz8pkxAnv0&Uch%)75EWE7AyW^`I&OKVY>uID209LWSg zpOK_nobaYq9$)W$*B%_>W@>oHi@uC=R2g(#W5n-?9pQ6DA|1TxUe11gD}FB(9%Q~m z$w+exkqeOsq};JmUS|!6UnDh8C!8}IxQO5Oec0SO*m_nuQsUNyck*|jm;xF7?haqV zV^*0ukq%@swNRm!`$djaf~*4U8}4&+%dTGI^1SraT)A&Wfz0kA3RQQBa)fmmTMW~z2LeIs5nn`w2NL^6P)MxFDu@{+dyyZ~mLTw`7`LwT^ zjG~lklt0b%UTy&%+aO0hqvNLvCaDCdUSQ~bzIGJB1m7ok9RvnjJJh5H&D6FC%a&3D zUp2XhYu%Sw|2W3OAJKn@bGu8S=EzC^tR=gncEBgom}J= zIK1X4vs@i_dGGZZWU~IPi+?*{5XpkI66kNxg|E$2+0h=!FlL?d?n-*Zov&Z(bvVTv z2}k7vY=Z27p-Xc(JyS@R%N0A`9=0qkl?CQQj=7?EFLPuwIm?d@sKL`t1PHYg+9jS@ z`T}ia)$vs_o>Qn3vJol+T!LP-nq$QShhB5(S4q4<1bBWN8G})^VZXgd$?tTEyT{lS zawktR@r$=WVvrN{hK%}Kdx4q~&cn`dDN1se9R@Yq={`@1S-XN={*su7E;w7ccQujU z8QVLm7`!quYXVX}!=^cV8Y%q^kB8j52Fdo%Pd^@iLAOrwR@0B0D$ID-3TrbsyUlEB zIjbD0c5Yv3lUA{~T9h)Ga*R?#GM?X%HJ5B!3#mV0>xp~nIe$0wIA&4aYnQC{vq z_9l84*mko0S?#fuxc)5mJJ1e9JxTFPjzS5b!3Ba?dQjBDT1l}@pz`FGvukJAD78q1 z_#ZCSGP9LMPw$E=awN3N6G&)EE3(%3UhjFHbL9t4e2S_O4ocYj{9d$tZQA7LK%vmL zl{Y=%eCOp1f)Hn2p(Z`vD69f1O)UPnM77ysqrq|RVbl@(o^ojTOETR@6AC(nw{uPK>)GL)!f-Q(LxjvAQM6qUn%9GAzR@HQ#w-!^zKfS<;fc^7`$dEGuL z=821xLX)EtVnC1S)@=`4DUQ2G3*VYIn{Dhpn=3hU!1ivEwVdj$N6h13b{W?dbd%sg zmu;xR`IULnzG-=PO46BbR$DOLFeh#28vcaO&$o3b4r~>p8g819EsvEHu-JU^d650e zEC7oTc7<$KM3gsKS^h9KtT4Eh&W&gOvJ>rX@szy$;Zn;oXOm9z-tlXk{GlrK*ads8 zTZUENM^`c~et3llhu7PznEznJ<3yL+m%%FQ31r9VQ|qGZX_q_fd5`NHe?-TI>$vA} zG2Y*A$LSwreKox|W^V4^UCLur?~CS$vK+;>J4(J^nDdO7n0E zLd!BY21^Ymv4Y6IZ+h~AR?%3~jM?q0#`9k+8Ht=KUY)BynZ+28&zW-JsCs35NRi{L4^V@w|u}} z8CG`gX`qeuTa_b6ymVO-NfiKSMl@$q<{P4-R|wl#PT~E^LiG4iw!s7zR2j$7szJFS|0^`c@{>g-eHkbHPTB{2;7q)+$azw zyaY^XJ6mp^oVDE8I7*wDnRgmPW&4uTl=C$C1{<^->0SDI7w`G_pkr-h$k(;$r^iUI zamEA{;Szpg^Vov2^5bsk5??07_sbWFn%c6aS+AHl`a$89+)&zR<;*pb@2e~YQ?Gn* zHRj?YIiS}A7g~#3QLn|b0|<9*Ej2Npe2u2`m8O<179^#ix==EWeeBXij&dJ)e zK@GxGpXw4pLH7p_e&?_`cA*heWxA(=5i9m(+tJXmND(Nud={ZON)k+Ki871VlPoZ6 zv@@s&ET72)V-)PL)32JcitqObcOBST+Vwm+SIjWI^gOoj)A8u%sPRl$2RoG0Yf)Zb z%OZr*y(+^V6AfvAM-l8OvWYCe&xC9nRR^UCW7ZM!nnCV<{pz`LKbTttO65NXWgfY1 z+dBKI|4v-@BNn*WXNPx{jV2*#G$Gbe*;hySL*^oHgvRrg^zMo3-FJ9)DZSmL*yGIx z*T%Ptu>f1L@tkWP9Xj5fX<4I6i%1=Rn6Z!P%Za%0)4SJWq(=&@tH>3}W05HSbFsU} zPRv~9RgS3HBUK)zvsA*$_PG4-3oKU0??)<9wP-rIn&cPWw0yTU6ZR|3R=v-F^1OZZ zMx~a(JcNu`rt=$(j?SV^^xv!|jDr=n@@BeUQ1o(+u0K7xS%%KQj3^x?JY+(90eLI) zWcG9!;$#Y4E!7?yx3U|E6(Kl9EmSNXI_IQq7rK7D2j}1hed{ zEiEOOa;97Z>0&69S&ZR(t5QldC@_ZSsK}qNg59h;LhvBbDohr8~TQOMP z!9lo@fD z1m7FNN4$vv3jv-o$d5F#u?_cJ=IH|W>1Lwd(?BOGmm_-~PBwo4w($PA{4nPc)N^(p z%Ri2ye@-r>c6h1i&X!D&Xo2k^NHLpuBjxXlFO1q1_Q)iNYz6B&li5|vN}B5WqmJ4p zA&<|op8p#9>L5;r+?uQ{p;2L}#q=;}mOMP=k zL5e{75ydS93+BYd1Ap6tsMur~xDid&4RBt<2w{$tPF1)yO1!5}()& zQDTw?I0J^)Qe`^R_hjAa8s(_k6fmT1f)mA=w`^McWzHLg2PVy`dYomASmL9?YTEm( zUipCC*~`3U{b|&2@lp)L!=#zPrRFWmxcxO9bdvy(ENvpQc$I5dCip5u!n&0C-Y2|q zou;{j|83r$jlJf3(>KZSH*pFM2jCvvixm*6w+u~CYZsP#$9Q9Sar|n)2FPqoim%=% zteS@4Ra&vqcJG9fJj=3RE8O9j*08xYUtpX{qxe7?xFnb<+Q4HCGvYet$%)d4q-AKO zm+wGjhlpyn(68T}sH*!hYBus1z$C;nvk)MtpUEk1DGX@s(76Ho$*dSG$OZu^X%^$& znP^y0g(2-87hWW4fffU2xwS$pqa0uTNe)6!R$55 zT5E_P_vnDefrt^$*%fw|3$1g|lH`p?%H+N5DHEKmo4g>Kx)+AUtHtQlC%7lwIS$-$ zg8^2HZz>HI?r%i7O6`%WWw$>VetzQv_t>|B^wdXU{fhn9b^5A&Y&*@yGA0sBzsbcK zdalTYaA{w?{nB)A3`cMATK?lTqZv+eHwtU6)aX0p_${?kEN>5q} zB>&8#^yXnzgtgt3VN$xxW;(9hcZvCRpm;wrCW4wG`UA4YUFHa+Joux z@AGTkH%9ur(aC!NeIS!f9ulN@c$M+Nr^)jJGZ9nd1a+GB$Vy-XbFZksH6++!R3*RS z$K-(nyaUK({Q&dfWj{aq8YY5?>DI_lW2T}H>5Pi+J%U9@FM}las$ng5rLbM(u>wQP zXKS8d<5^Ao2Z7e3wFtG`PrLS3m846V_#=u34-zKj%@@`VD!+ub``Xg>WV1Xee7jrJ zgCZyqfa*(jWOR|bie5ldSe6=Q&{zIlzgYGTyz0_y_lTHkcGO`1b;&@#9~*#Jqs+9L z??|@QE<4Fyq3`Mh&h@E>8EW>NL;J2fgu-ZYpAY9^nEh7xgTAXEGbG$ z{MmOB;x zg~5z&M`T0fsc*1a#DU7lqtC{y{25E62$ebQb}Y9K8ANv@EFanTOn2ccme@Oa&%0NH z04qfd72}FQkml&-&Y7b65N&Dj?ruYy^ihcrJl$F(I{~Wjtz`Q4pk1lhgcobD=U`bj z@c~bL=NsisdPK!#0haQ}=O$FTDsYx$OD&YmVY0)7YQsF@99Z6Jf2;UqdbZO+zM*Dr z`oZ0i;A_sp8DrwYn^DBS(gkS$Z8!V>ZEgbsHckL{gI!B>%+~x;H&}C&L)c^njo{fz7AqQT9T`dnj0)Uv}K@m)=oaEU*uvM^b9z)$3ybF7 zqmWN;6_U5s$sLagu-jc{8@tqMdpjTW{dmMWzz?A0?x>K~`7H6~xiob1f^CB7nEb01;t`nPTV=lHlL)aa|;mG{L%nB)UqM4axsMW+|o$ z8__4{x}azRD>dsqlQ(%`lGL<`XhN(^cM2h8G8ot@N6^wfs!9X-!hSqk?sEstAJ5eX z$bz`$LlW8PB|p>o2jpz@fLV#~$aN#kTV3i`U-6uL+9MtL&zD#H*_Ct9GT7lqgMunQ z(uhGB(=c$mn?}}+x3v6e8SkLVtOHi6LrotYV?J|I$1(#O76>F-OA5a#X$YB5&HR>E z0;ffe4y4eM-nYGnGG=4oiq6z$m>eRbiV$oNz!!0gV%8l+Fr40*R_+w? zbN&4;)oeB;bEJUB_>FK_jK2>%|EU9p0f!@RQ3_*VN~Fa_+|v^x}a^Gh^kt;Z+nnHbzFBJ`^xqnsvWxN9xt-;Zi~B{ zb2{a+%Eo#=)yH%uK#9%T$fsd^FoMq4-!uq5A=clgbp30(zr09MHw!ZAzx4(OdyW+P zlyO&2o^Qyi95O)JevT(I9eR^@)4&0;-uYC`*G2Ix#LASbNJ$Lr&_MWvL}d@p#znUG zz3UlF%~yJ|)_Td^!a2WGx;#y11YIyrT9-+raso}npfbwc!iA45ot(3U;}-;@Do0y< zEibp<_cGLoSoh#|Be6=z4VRDQ`9iqRC1|EV;SdqET(>O;EQ!%GqXI1A>B`AlFLW3Z zLisH}d0TFvTGrBz+leIrQ?-lu6R~;U8mi?i8H9s^^0aHMhqc<^G}X%O{B7`PVd_Mm zh!ySxvp>}mi|*tJS`t)IB~HY(w8Ev1Da5LEb~_a9yM$aFz7)=D9voNpg`>Hj7&%A76Q zLHaX?2jt1HO+X4rX-1!g*MlCZR;Z zu}O9)kCrUTkbSrh)7UqE&gbe>*3e}*_kRuODwbilVl9G>BYyi)d*i@2x<(LtVzY2w zcgyZikDZBPakMnOia#ZUFFi#n0jv_ z#$aH`uu&+#Z8J#;{=-<-6E{S&(#0?}$(G)b0JkE974IVQ2OKSo?&|el@qa~LRH~5M zMe)TN7Th4Ub&-5J1A~%a_=GShqSJ4Lt#5aJakV08HniT^$UImngtJ1JM*GrZ&08$t zWynW=tgA>n+im>?`ji-?YJVEM^YQW)+FUW@(0$UE>{4$4UKM#x-_MTh>dh(IzP8qF zy<9oNH+%AU{b+3%nA{r?^nO-)1X*t9rE@_6X5@F$9%KB06EFaRs?hPS5&`6ZeL81ldI5mhvXub*rQKLms z2huV|l`9cpT4%4~~GVD-))@hYO`cL@`3~ zqpe#V*ju1eQBT#PHApM?jIwKJj?lqrZh%E#N)(-JkyPkRJ?x{0)Bn;iiQ}lLvJRSY zbMwH!7CtFEs6M~cMw_5NDZi91qlN=p{vk3dq@WyIC*}urpfO(*A9lrYj@))s>eTbw z&$9}$oUT*FA^$l}yiv0`c^|v|fB#^qhQg_x**R>&>Dn;Sh$RN#^IAuazfJIP`>4jN%2-^T%s!)4KJ~z z)5v>Hyxih|l_9Ff5-}C~x|q6r*@mEpf$_pg!lPfZ!BeemT}H!6u!DOakuuFy(I7J1G`2~g zWN(2dAYqt7gX2cgrjGJ}prFC2f&byD;>rUerh*rbs~;jsQJ1g_)4b7Cz> zM-w$>nFnaqPImsDhCF}lkhg{dk@x50>u`FQW?VW|&o55`msFaWSa;`E)-9tc?GI*W z_%j<=4$a0Eq)ir-_cUG(YPoqS(#pSFlBX7kg>&d;D?asUux-MzKkvTU!);JRL83LT zcxUh&E?K}q5rtTC2(NNZAjFtNBl64JXMY*%gzEtDh*3POEHwdd+F8+I$~4K2vMZ&(0dZ3Qi@0r6yHdcjBe01D-y=-!% zCPK*0%jhxTUbgc4USe@0SqSi{k>fQV^WoGVJ^ebr&aW1lb01s3oVxz|(_NeQ1f85p z-7ISpuO{E9exVkAOFOQ}beY~XU=TfHnn*8>m$$PwJ%CDj;{3SRQNzphQ3oMG^@RD1 z)8gB)xc|O5|4(Ts@Z$pjV5Kft{Ejyk!)22Au|&%$Uz&Fo=UXf+N3VcmV^ng~-fbj$ zj}XK52N0T^DjE@c>!r>eMb=n9_{P)pPCNzxaH$OE>t(=VhI51m`}3g=yJ4?fCm{;u z3DPwcph`s>`njG8aGFq)q&DU9%4e_bCE)krNS?h8Z4xg}Xo&rOd3&-o0pRLHLjf-E03d{JNNy1QE1xEjL$YpL>!QSs!REt} zVS+6Zm(VveBOVD%jLBe$y6qYmBOunT?>fPm7IWmpHM2#CqYvhJg|nY5KqLKIQo`q| zByk)fJ6Ei+$fS#Y2ib6>W zQiofQD_usvhdKB0Z_PEL7QRXx4P*E7-4nbUCQe9$F$lVHPiff|FW;#cAUqql1r4+K z^|ZbO3!8q&*M^nqN@_e4rmD;D@Zc)6RnQV*$Esm`%&&mszgV1!oN^Pp&h@q4JuxP?|eO&!P7zw6;BiepR>zi!V% ztNl!*l(vxq7o^d+&J{%un_;cD;*5+cy*wen1ur`Br?&4SCFCyrt2%`~@nNSIZ^qNb z0KjN50L$?Z9>`K@=6{sC7)30kf=^&y&hlH5?#>voSq!M1T9n1KXx2G#y@O|K7)w8m)KJp|=%d6pIOAq>RAgl>3fy?_RaR~_ z;!lPwEPaR9`^p3UUH2!Z=cP$2TYjg!8-2E(Zu@dNqnNew6*0UtFej_!Q#zgZkk&DM z6=&S$7}@wFXI!Ln%s}B#nDeOD@`sXWU-@g8F3h~LPP#HOr|+Wb->g&5?Y{n2**9Vl zr5QqE>ThNp&b+PTzN)xA^dCx%An-YJ3H>DXU(CHzHSQrTow@KZf!<#eEpEomXCo~S z^N0f6{xJ}slvpl+bC;mOw8q`2sIXZmho->7y|~3~=P)+?@%!^woQAw8z$hst&Yl)6 z*9d6Xy>(+jF>*BEk1C0~aO_BE^@zWY>*gBO$KzfG9cyV>@0~wi*7Q{Npk_(UES-}z z+wNP3{xEdg`Rby7rP_D)2WeLtp8ki>TBw10nFe}~PMel(r;@2W)P`b{+5 z@)T49GrW9ewvrpC2d7%{TDf|}rz~A$GFr=I4RAc@`yzlpZVotc7VxJHrQ9|N?0whM zFe#gFv7Rp8+}y0F=@LlSGJ2S>|GwYRga!`r{rRJ>6Vf)7?spzY<7eL}0FUg;=@Eut z8q-~Kb90UFALi%h=QdG_Cg3{0iyJ3304*PLNDI~5N#>VWluQEezw+V z{V}noR6YUN!fW8H4ci{i)4jV<)K%sI^Ie_CXM3bRAY;Vh`+dbOyI;c(A*(yx9Pa~< z_SN-=xt6RiU!r|2I&UC{hw)`B8H>CA$L8*yhLGFxM6tX3;pg}j4PQR9pp2u~bpMK3 z-J~f@UJGbh+nGVQE=(;b)BZo8TMSSk6V4f=;z8J)Q5|!S>b#Wud+><%7X<;}JQwI4 zusWS18%{01l6Ywt-VIiL1$nL0-*^_Vo|pUP-CtOK_tuwS=)))yN#cB* zZ8a#QRFN=WA0Ho&-o7KwGn8i)dtF%GWL4VOv6RA2 z!@2m&XbY;2pYxF3Vw|LAUIXDJUAgC!F&9=0+Bb8K){9poq;hEuw1-WY zFzLXwjHue6Alu(&U>4eGryJTRANS#bzaI=3^I1&jZI`sg0OH{H`}|oVFB&fIRrl}aF>TQ! zerBOz*~b}Wx=WTjlgI!#iWh9$XB>ms7Wm39F1cUzJY&o>WMkpMoA=tu!yYHozkR?c zd1ie2MPY-q^0y0*Cj1k(7$ye3&^3V~%g~;?e|0Ux4zxeTiFDM&(oRn18BL65GEtNC zj6-%e+rN)n{TH7xDj?qdWZVVq(fiIdyY(GdDBzz~lFs}xV_KKtz_$I_titWhR)S1& zO2&{^Gr=-44T4eWDLyowPp`jgxngvHCqbl?ndF3AiT8=FgKw#(-wBtqI4^!D!?XDzz!lAhwAq3o8sIK!UhRJ zbWwMA!1hoqDWBc`;ZKeQlcqys_n>j14ezU>&z7x510O%Ql^lKkQ0HK4tLM!w;j(F^ zV`xd_Mh7H*3lcuHaZ~Ah9}5HG zjV~-~f$qJ=Qy-U>mv7J312%dBK{X_qZC(snh3N|BD{{QKOqP4wV@Jw%XMK)z-!Vd+~xmRzK*t? zg-_C%YUz@4V#9x~uC88xd0te*?pyGf5-K3F+GzNhO6mMZ&_oXR?fR)IsJLEl}eUR479ZYSi-6rDO7vRy+^ z{?}~?uL{5x1Z$vSlZZ>=KUWIRCfh?o{`A|en?5c{47vJ#w6eCozpe>jmBj%zlFU#- zl0~JUGI^REfXP_eprZYQS`28^HIH?_BGkJ?wAOE#i+p5{tE5>CC7y?+P7g%wel?Jy zgn;K-aO9mbw52!T#2sd8m31vFEa({+AYvQZw=oR&x8nbeS_n{|xCa3>Zdaqg4{X0qvw zcQq{9ENLxk-e`b2ndLXmFvF!JI21}hT}~*nn_#f*m#b+I#Gla&@5GCbQ%GYh8Yx@P zd6P{P-kwVz=V*=mzAjo-6GKO$?)IK~33ik*Js07(s26y-Ff2DFoBQ+G?`race>Vl- zndUsken^oj0i25=#lC^{=z6p+7v=ps0FP?@cCJ5z9Q(agpT}UJ;A3QJzL!slgascA zdd6L3kV_t^g0^*f03&)cC06siSm|+|qT+6+U+%U4-DX`KCqQW`z8=2Ep+Xhk zmpnX(0@>j`sw}3R%?0i(q8&|z`k05E*YAe-=c@G5-w@(Uq9X+JrZyC9i7Qor}jaoH<445scH3UA4{E!xhePM}T)fDGOa(7n%}K#GA;K}oya${7@9m;$HRUzA1rI2t&PA-bziXAgg2E)?s#6B&rbbM6URi&kul$z6 z9HlYuSjY6L-}l7U@bOFcJt?5#Y*$d@F}A2GK(8)fc(1BYpU_>C=@`5GGw*R_59ESr z3XW{7xVX3sO4%H{`iM^3vHT=PP-`+_@2i87ytz0|9czlb&)&Wxgy3d}R)Hb`h(pWG zW3^M5!0P{e!aA#SiAYGcy#9d-1M+e1W|r8xf30x}NTHb&HKgIra$!<(7oPpZupb)# z^O;=8vo_-o3o?yPgT+`FI;m2w&>L`bGJ>UH;h5S?NAIoSpG#nt8a6m~f=cS)+J3v{ zv+Cec>_J_gu)Dka^($5UFgG_h8~`(=3gku2M`&c^wlzy3HZqLwgWMFD=D-~91XBbo}3`eO0)$>*dA z3~}4y7OL{R;^Jab#|~^jT3Q+q8>~$j!*N>D7D=08E+v5YHk)#X8P}KZ+^)D%?AV;J zjwg55=%DHT;27oaz8^~(%9m6RizVehU23xR_V)Joe+`&LUGcs{NVDiy75`mR2*&pa zFR!!2ku-GJI;a3eO=GsGDqquQ@h2uK7R<<00wp|8^e$Mml+TeuvNuDa`0LlNadB~I zglvC>FoBN9>*iKGGVaiC8ReRqfWQEH73noR4ujLHWUb>MO?pTv(8VmMo&*3T)AtMk z7Yv?$Cn7=n`}=oSC8DgdQ~Ud+<>fGA>JQRAD&>@MJY65rn%7^y*DRCJ4LMP(gYB|fk591sDp0MiONY=DD<&R7f$rl!m$nt2WTFJlZl`@g#WK+W+TsRV#|>xD|n_vhw);z7;JF z1i&8-cCTm|jD2HM|EgRb5TI2uf{m1S*yK(Xnr^JmluywDSJ&68AY>4qO3b{ox3}-_ z@3&~UpDL0sV1&ZNH{OTOE+12y>*(pL6oDIU&|zY(HTk*Y0x|^3r3@L9vd2I;q^ztg z&9xvaEGII<7Khcpe`^_|wzs!+hsCHOVAH!U3kr#hRS0<%;t*iqTa#4-Qc}SVBTS~{ zV|_g^F+1DjxZd@OpNa%IssIrQ$>Vro^YXs9q@+nQle%-PnrJwKT8Qe`n-=v%aN~d2 zDT|UgXaI1niBf1mdeDRQK#XorS3w^71256U9RRRX{_6$!YbV)n%=e`RjGw^3z`;SO zv2ZBh8F$^tCyO^C?L|FO*9ZcXVVaOnemo(aA->C(`{vf|J&NZ^3g_%ipIAyjDf{jD zf6#lk6`h=%492T_`)Vz|S;f}pwy?0UX1m3$%X=`I8wW}kcdYGqiL;{r7P3ejz{7-{ z{=CT0?%*#W0DxYX3hN&(*+ef{1vxE9Pk@wO<;W}GXs+t6Dn>F}(UAvtR3 zRacuBbG!;4vuFq7hYTZp4$r83iHqBi2}1P!RMUM({^h&dksV*Kd$YKe(B&xi z5@gsJq(HY|kH7dzh^t%C`f$F$*lRcrHQ-!WA0)ZT zswu{2QetZNbLe03c;Xuo2i`*d&b0yw&!$4(Cn;j3wRG_yE$V zVmn(+BJ}|=!zb>y*Vop=FsPR{AWI~f1d$864&uskvhs(B#8Lh;-U`L z)||UV5oQh`eMO@1#R3W@_y41@u5NB_9v&aQO*UAL9~QlYkvfzv{T8#2hss7C=FyIn zKSv#X0gE;`B#iwM&;>z$b`y!qa5c3)Kku<@-=e|jf&UHMf;U7zWChCqG5-Jw*7x@I zJfG7xm%ZO~B`#zhU>?+TdK|l`vw6?^1AfX9NC24RJv?q(x}Q&Wp0QROZb9x+F-$sR z6hk0pYg@tTC}{>c3C}lGr+`U=%^r+bs+5(5-^376#Co^eJ}?_$g{m%p6B8TYj@^Y} zYlOass&In6CA=mn01iFY(mk502Hq#K%l!!2Vfpv0KtVZ2Ap&LES`b(jF~wkseKviM zB{_726+u=i?5LuklLTu{Wr3=46lEiDNCTcpK;uaPRs>UTzA7aP_6!CJpsGPc3kYG) zVE@nQD%9#xf(q+iZMjRV5DU{W2PYLL@JHg(4MKs5Gi3kVA3MUPr<4Ge-UtT@rD5!f zmOp$aMOhB{2A^OBp_N5N3?AyhQ&m6>i&DAV3@j^BcL=tBqz zILUBCznB1-0 zOIIZ&hDS|;cxUKFkh7?Ylq%b&R+#t>b-mN^LS5eTvE}#0;9sA|_Y3_d4KE0Z836Hw zg9ye9M&PaU7Ca6>%^E~WCg95WE`*mfvRYVF4$b79f#p7d?CxMI!+&*6$+#U2mOw*8 z1E%nm%I5=*aqkD^Job+VNI?C-H#r?n){E#IO#m4ly7xQh=ds1Hi@gG&+U0bwy39yXCcdjQfU$FBS0+&<|$-o2mZ zH?*duDDXg9s3*rM$a=;%v}nqc+uGW~Dg58IUGa~N-sTBdiXDXaA*k%*)w!`%l81^#>Jnz2@$Gaw$f~F3ZrJ-6(K~c(20%#_-$JggMeqZ;PtrD z*R-+^_u~Qoz$H<2{)aZF!k3+QUqsE!_?WOt#&#n|^@sr?Fy~WHoE;o+ieN_k@j8C*YxwJjF-QMzB|KxEeA!)M#9T|skw1}PcxIO-zCu?E zb*81*r9Nw**&ffj4f%bLWer?z)E*_xM{~`XXuvU*hUY`e${#3R&M8uVBK83ocVtDxK3A-DuFB4sUdb8>#o!AV*X*P@|0@(_)^!)aDSYm!HQk%cM>6!U3Y^q*~0nK;>UhZh)lLIbe zE{e_|9Vy=&e|s?l?j(dyTlki?FYYMYHIntbpTCHa#AT}d z7#w8b0XiNID}sZAwLUpZ6>*1ZBj}`IQiwb)+14Dq`NTNo;@?r(|J2kXYY^9H@QEwL z{~|f>&bRC5E>a?S&HQ8MnIAhCKFVWNf-ZI5^lhJwGf^6GOcf~6)znHe@Z6mNO60yw zZcC7uO#Hr>jpSQOw!68Ci4hPkbJKjMkUvE*!-NAM_DIrYQkz(i071PQAf1jQ7p!e$ zmrzF@JNEi_zn3257DjC=+BG0)`tF7*5vbRg5_+Axr%Jne*n4y>0#XBT z;i)Tk%GayCeN<4v446e&H7Q7ShaSV7|X2|svU;i_Q_o_zJ zYYi_-lTT~#V=Tx#1*n^I3}KRY0~F8uA1*I1Q^(Y3zA^p;%X~sn)!E&$dtS?7RFV@_ zw2VqtW(_6);CQ?%j=WQwbb9*~O!3rn#_s22Bne`y(|f#aG{W}rSboFYU~iNYn-~Cf zrqh&uUxtNDPg~pbxNdl2M@;H&|66<`92dRz)ZodRSxw!ckDaVA>B|tq`JR=PmD{e{ z&K+`GtRbh19odr}@)$07)y2`2G#f&|iXje-I)RjgaX}QCQ#J<-u39lRyrM zB#Wr>z|YB~P3Pe}%6H@q=V|;Tu_6~gXe`HbtL)N9kUAfb06j)j06tESXlRJz)5-XB zx)wmkgZ^dM?`Dzi2sbo^I|ftqNkz~JzHi{7i(Lv!GmGBFtGp`>Z-wi$AK$HR6&2VU z&#Q5DCkc%g{ihAyIDMiVWo(f(1kg%FMwuK8hy7%!Ug8LJt&EgmN|b>gdqvbUOc78y zeChm9FLgoXyz-NSFU;8XCtEouWI|OnkE#o_syX*G@;+StCxv{vcj*Fy2F^uS=A!|SF0qB zX?1+IlhRHC7&`mOTTwnf&Bf6fe%Tx4);p4<9%p8WW$htJ4OLqeA1Eb4ri>j&aAp1l zmF(2PbV<{8BV0a$1Wrh-xgAS?Lh5aKsW4bGep#@rJCNrB>9bujj8a;qx=XC4QK_CKQ!$s4jgE~S*andQsSD67@X}ZSWhCYIQIUE+U+Gm( zjf)D3gu+0zIV{!}{XmkYP8=B-Q8TMz+CMvEzd(6_=b&PqLpF4cy9gd|CjDgu1hHJI zd9_Sb2t%ywX$c8tN6)cSV-y!$NQi0^&aAFxOCC`l#f_@`=k{W$G8=^^7EbF#+6%xaN9v}e^lLOA z9IDtK)$xacjjh3GDO96*ke9ljFzupvth%MUZ+t>l?VB1;?#~Y^*XNr)o%!8;Rm85r z$JHzX*u?!URC5#IdL^6i_BFv^l4NnI252Kvf&xuMfKC7VRiv_SL49T~hk}L%;N;!E zTj<GPT4^_}9;1!}zp*uWg{~3y`fs+S_sK>#NKBSwsp#TyLUA;2E%@5P@ zg^HnnBk)oinrXe!rEhK85hgIeo*=tjt5HTbyOU=VF961W6>Iu_*n zH3LjmSa~_4r&Za6B|t{3*`pqsnuY-#=A~%w=wOngfknY1ejA``Q87bEg@2EAwOM&c z{4zqAMaiO2ZOdib3KoNG5gl7{BmengwNR;zfh}bPy59{RvcC!gdAkN5 z!*!s;i*~<|D&eMM+3axv387d4DHcZf_38+-B6>P$C(@@Se2!T_5z8XXppCwv8KEZ> zzOw^#G#NI-u1ZYUoC6Xi8hn_wEms?DP1F(rP+acXn4Cx8f-e*RzL* z?9X%ntpj2Sj8Sg8vv#x(0Aq3Kjg1?G35k~-5C|#=O#@c{)rEf3)_(=tl^H_=o>|geRWOdRCZn36Fyd^-} zz$he%wfScbssoqKEcC~@{HD0PLHiF>CcqCd|A7!l$C`txdvn~-YQ2k;bb(=CWT%8kb>Y2MDv7xY zHN`9i!foItRHfJ1Y(D1qht?U5oCB?in%0(tC-!b6Ee+d3L;|t2YyoTlcxySWZgc>j zQ2eT>4R!iuL<2aFMkD`CX*T%109#uh&|dnLgzJJMHp}M9!hcNzv-xHmt!}T%;0M4L zaHyccWR&q6jig&?zY+kG6Y9oj6Jpg75mEm&*-IpQps64fQ$ba^$|vB(DdoiCc-{I@ zfF>ib9i0Nan-W6#f8PL@cJc!x4@$#{!G;%I==FxY5(HX`v`T|T0`~9|eZX%vD#eYl zvPuB{_a%U`C=oIItgr|~NE6uJU(BQn(?x1B0{aET$b&8HG?6mJz@~qc|Mv<1y<@Pi tS{d_AEcU60k_3t8RaB{cRAt$V=#~M-D@|3ruV5_%$Vw?mR*M@2{U0wNP!|9I literal 0 HcmV?d00001 diff --git a/nevmes-gui/assets/xmr.png b/nevmes-gui/assets/xmr.png new file mode 100644 index 0000000000000000000000000000000000000000..e924fa8b292db2710b8604dee0925331fbc5fad3 GIT binary patch literal 2893 zcmeH}c~BE+8pa!fas^NhIU=VDgB&@KD+ma31Sg3cfk9!HGlUpI2ni^cBBuz-fGiUf znL$JZf`nTkf`W(+C?JPoFoKAnfJ7ytT$#q&t+l3Vt7iY*+OGb-x4QfHKHu}c^>tTg zx#L~sWHe*|0FZOTIC+Bc#O9EKfU|t-{RuEgkQ{Ih08p1JyB4$syw(Z9c;WycULOFG z(*R%vyp=o)05J#vcozr&XchpdMisYu><0kxWA0djGYE*n04x>@&i_CE+YW$z`-{i- z%6~ECe**&x2MZC2M6gDiv*2#)WqRVhzzC62*r_G4!{Tp|=dO1=9G_GbS1}(JtdCf3 z$K*d*+(4YTFN{N>i)Y_T+p&!=EX>2jqFzq4t=d2%UTGAqbUEUt@Ltnz4!X3<(ybI* z*FKr>D^AwfCs%RatHY2psXcCkL;lp$h2py<^QjQAD@)|?Qo)Bioq<~@pMO)<)?y#w zPbzWS_P?@;Sc#G?((FV$89q4Wa`??O-U~OC1keC!a`L#ya8ZK zo12pZA)LQ3w(3W4R+AJYNfut}jlOWeA?Q|tvSMYL9^K=~Aupr~)zBQi=WO=R6ut^P zZ|e=yt>yZKs(ZH&$l!*!g^xBo#1`sjP-djZ)?J_X+pex>Y5UssCmw&?*OuL_h_{+d zQ~DinaynXeB~yubE(egxQdfdm-rYevjKLn#!g~g%*zzrOP4|TDk72P^QXZ8Q+DRLs z43p|#6y~9~->=~J8G1F;YqAtw<41=Li#jQptm1KQB0F!5s~VvA+}a6mbV8jm{f`uK zx=uvX(83tYX3Tjgovkr3^6Dw)M&A!5*0O!H%KWEw?+Xf9<6(e$!%QHg$gHg|2g>OE zvF1md17yfK^GogtO(f33P{COyusZ@-APIJ zJ-|Qu+hMK0*KSjtO8+E$0MMQWI#kAG259m7kNEoPe_Eto_f6Xp=CQ>9nr&1!Krr3f zexcWumzdpHIW@hrsc?T4KhmrC=;3m9{`|aDGqjo{aVk#k>acp|oM+&h zaEenKm{d?6;Ii^jS2f6U7O0onyCdP&(Sp4QHk{&moQ&WmK z4Y}5|usaFs(IuS*e|hKP1Ben&Ux-aps*$kTnr;U5NUTR*6-kc846Qenwr5B1j(H2&Vzj|HldXZ>7t22^v&CwkT1(~JC0Gxb z!LC8Ymj(@_jGp@l`!Wp64j>--Mp^$se#sajj!T3tp?pW08%Y)s zW#hr>WB#1rNA)E|QL!0(1*(j%es4lp=dc2`A-dZBLg=!VhB4W1uwf^^!Ea@`ia{2A zNxhw3f6slg&fF-WunYOIqvfaNj?iIFfRE5P^-(5}+g$VBT0-#P;)rdg_~%>83%hRW zCMT^fuWrCt#Ukm%WNo`ZsLSj`X;;&C0J@a21MX(#y8a9(%og60`YN2!-q9cM;)#?G~({|vLg*kwGopv9{= zVKjT2sB6vpmmgZHVY;?G51zg}L+`)rig_s(J6XJKS==!`_c3itqbb33SDB?*>l1pPfXAbE-#d^zFw#tMc}Afc zc!72OvBiM}o1^b8^A2_hvqIN-^N}GlfDm)*_4^1eFWaeMqqb&o{bYxTy?Rb$nRI%g}9Gj`dt-7?_h@so^2Gt*XcaB^~IS~Q)NZ7 z;;2q(lCcQTH3-m@e7iRW{IWYToqd@hG%A%C$pl=;5kwsZEhajIh}H>bGNTx1I6Q(J z8FM0<7)vG|XPDBWN$^byjiW^pqiH4)G!o4;Dw4D>K4%bA+q0<_8pI@`L!yab08j`N z(gd;31c@LZ&Cq7%XfsPA1Okmf1o^+%{OkL70+kj@4vYKG3ERYo9bkga<_Zj281r~g zG!gihM=~{tL<9#pgtBRo+pl>UWYsoVLU;@{7>#zKg~V*uh(^1EC<8!Nh}z&Fqy8P^ zwyAg+H8 literal 0 HcmV?d00001 diff --git a/nevmes-gui/crates/ecolor/CHANGELOG.md b/nevmes-gui/crates/ecolor/CHANGELOG.md new file mode 100644 index 0000000..e278e36 --- /dev/null +++ b/nevmes-gui/crates/ecolor/CHANGELOG.md @@ -0,0 +1,13 @@ +# Changelog for ecolor +All notable changes to the `ecolor` crate will be noted in this file. + + +## Unreleased + + +## 0.21.0 - 2023-02-08 +* Add `Color32::gamma_multiply` ([#2437](https://github.com/emilk/egui/pull/2437)). + + +## 0.20.0 - 2022-12-08 +* Split out `ecolor` crate from `epaint` diff --git a/nevmes-gui/crates/ecolor/Cargo.toml b/nevmes-gui/crates/ecolor/Cargo.toml new file mode 100644 index 0000000..5ee7f12 --- /dev/null +++ b/nevmes-gui/crates/ecolor/Cargo.toml @@ -0,0 +1,50 @@ +[package] +name = "ecolor" +version = "0.21.0" +authors = [ + "Emil Ernerfeldt ", + "Andreas Reich ", +] +description = "Color structs and color conversion utilities" +edition = "2021" +rust-version = "1.65" +homepage = "https://github.com/emilk/egui" +license = "MIT OR Apache-2.0" +readme = "README.md" +repository = "https://github.com/emilk/egui" +categories = ["mathematics", "encoding"] +keywords = ["gui", "color", "conversion", "gamedev", "images"] +include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"] + +[package.metadata.docs.rs] +all-features = true + +[lib] + + +[features] +default = [] + +## Enable additional checks if debug assertions are enabled (debug builds). +extra_debug_asserts = [] +## Always enable additional checks. +extra_asserts = [] + + +[dependencies] +#! ### Optional dependencies + +## [`bytemuck`](https://docs.rs/bytemuck) enables you to cast `ecolor` types to `&[u8]`. +bytemuck = { version = "1.7.2", optional = true, features = ["derive"] } + +## [`cint`](https://docs.rs/cint) enables interopability with other color libraries. +cint = { version = "0.3.1", optional = true } + +## Enable the [`hex_color`] macro. +color-hex = { version = "0.2.0", optional = true } + +## Enable this when generating docs. +document-features = { version = "0.2", optional = true } + +## Allow serialization using [`serde`](https://docs.rs/serde). +serde = { version = "1", optional = true, features = ["derive"] } diff --git a/nevmes-gui/crates/ecolor/README.md b/nevmes-gui/crates/ecolor/README.md new file mode 100644 index 0000000..4c84da0 --- /dev/null +++ b/nevmes-gui/crates/ecolor/README.md @@ -0,0 +1,11 @@ +# ecolor - egui color library + +[![Latest version](https://img.shields.io/crates/v/ecolor.svg)](https://crates.io/crates/ecolor) +[![Documentation](https://docs.rs/ecolor/badge.svg)](https://docs.rs/ecolor) +[![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/) +![MIT](https://img.shields.io/badge/license-MIT-blue.svg) +![Apache](https://img.shields.io/badge/license-Apache-blue.svg) + +A simple color storage and conversion library. + +Made for [`egui`](https://github.com/emilk/egui/). diff --git a/nevmes-gui/crates/ecolor/src/cint_impl.rs b/nevmes-gui/crates/ecolor/src/cint_impl.rs new file mode 100644 index 0000000..a730fda --- /dev/null +++ b/nevmes-gui/crates/ecolor/src/cint_impl.rs @@ -0,0 +1,161 @@ +use super::*; +use cint::{Alpha, ColorInterop, EncodedSrgb, Hsv, LinearSrgb, PremultipliedAlpha}; + +// ---- Color32 ---- + +impl From>> for Color32 { + fn from(srgba: Alpha>) -> Self { + let Alpha { + color: EncodedSrgb { r, g, b }, + alpha: a, + } = srgba; + + Color32::from_rgba_unmultiplied(r, g, b, a) + } +} + +// No From for Alpha<_> because Color32 is premultiplied + +impl From>> for Color32 { + fn from(srgba: PremultipliedAlpha>) -> Self { + let PremultipliedAlpha { + color: EncodedSrgb { r, g, b }, + alpha: a, + } = srgba; + + Color32::from_rgba_premultiplied(r, g, b, a) + } +} + +impl From for PremultipliedAlpha> { + fn from(col: Color32) -> Self { + let (r, g, b, a) = col.to_tuple(); + + PremultipliedAlpha { + color: EncodedSrgb { r, g, b }, + alpha: a, + } + } +} + +impl From>> for Color32 { + fn from(srgba: PremultipliedAlpha>) -> Self { + let PremultipliedAlpha { + color: EncodedSrgb { r, g, b }, + alpha: a, + } = srgba; + + // This is a bit of an abuse of the function name but it does what we want. + let r = linear_u8_from_linear_f32(r); + let g = linear_u8_from_linear_f32(g); + let b = linear_u8_from_linear_f32(b); + let a = linear_u8_from_linear_f32(a); + + Color32::from_rgba_premultiplied(r, g, b, a) + } +} + +impl From for PremultipliedAlpha> { + fn from(col: Color32) -> Self { + let (r, g, b, a) = col.to_tuple(); + + // This is a bit of an abuse of the function name but it does what we want. + let r = linear_f32_from_linear_u8(r); + let g = linear_f32_from_linear_u8(g); + let b = linear_f32_from_linear_u8(b); + let a = linear_f32_from_linear_u8(a); + + PremultipliedAlpha { + color: EncodedSrgb { r, g, b }, + alpha: a, + } + } +} + +impl ColorInterop for Color32 { + type CintTy = PremultipliedAlpha>; +} + +// ---- Rgba ---- + +impl From>> for Rgba { + fn from(srgba: PremultipliedAlpha>) -> Self { + let PremultipliedAlpha { + color: LinearSrgb { r, g, b }, + alpha: a, + } = srgba; + + Rgba([r, g, b, a]) + } +} + +impl From for PremultipliedAlpha> { + fn from(col: Rgba) -> Self { + let (r, g, b, a) = col.to_tuple(); + + PremultipliedAlpha { + color: LinearSrgb { r, g, b }, + alpha: a, + } + } +} + +impl ColorInterop for Rgba { + type CintTy = PremultipliedAlpha>; +} + +// ---- Hsva ---- + +impl From>> for Hsva { + fn from(srgba: Alpha>) -> Self { + let Alpha { + color: Hsv { h, s, v }, + alpha: a, + } = srgba; + + Hsva::new(h, s, v, a) + } +} + +impl From for Alpha> { + fn from(col: Hsva) -> Self { + let Hsva { h, s, v, a } = col; + + Alpha { + color: Hsv { h, s, v }, + alpha: a, + } + } +} + +impl ColorInterop for Hsva { + type CintTy = Alpha>; +} + +// ---- HsvaGamma ---- + +impl ColorInterop for HsvaGamma { + type CintTy = Alpha>; +} + +impl From>> for HsvaGamma { + fn from(srgba: Alpha>) -> Self { + let Alpha { + color: Hsv { h, s, v }, + alpha: a, + } = srgba; + + Hsva::new(h, s, v, a).into() + } +} + +impl From for Alpha> { + fn from(col: HsvaGamma) -> Self { + let Hsva { h, s, v, a } = col.into(); + + Alpha { + color: Hsv { h, s, v }, + alpha: a, + } + } +} diff --git a/nevmes-gui/crates/ecolor/src/color32.rs b/nevmes-gui/crates/ecolor/src/color32.rs new file mode 100644 index 0000000..09e6811 --- /dev/null +++ b/nevmes-gui/crates/ecolor/src/color32.rs @@ -0,0 +1,216 @@ +use crate::{gamma_u8_from_linear_f32, linear_f32_from_gamma_u8, linear_f32_from_linear_u8, Rgba}; + +/// This format is used for space-efficient color representation (32 bits). +/// +/// Instead of manipulating this directly it is often better +/// to first convert it to either [`Rgba`] or [`crate::Hsva`]. +/// +/// Internally this uses 0-255 gamma space `sRGBA` color with premultiplied alpha. +/// Alpha channel is in linear space. +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))] +pub struct Color32(pub(crate) [u8; 4]); + +impl std::ops::Index for Color32 { + type Output = u8; + + #[inline(always)] + fn index(&self, index: usize) -> &u8 { + &self.0[index] + } +} + +impl std::ops::IndexMut for Color32 { + #[inline(always)] + fn index_mut(&mut self, index: usize) -> &mut u8 { + &mut self.0[index] + } +} + +impl Color32 { + // Mostly follows CSS names: + + pub const TRANSPARENT: Color32 = Color32::from_rgba_premultiplied(0, 0, 0, 0); + pub const BLACK: Color32 = Color32::from_rgb(0, 0, 0); + pub const DARK_GRAY: Color32 = Color32::from_rgb(96, 96, 96); + pub const GRAY: Color32 = Color32::from_rgb(160, 160, 160); + pub const LIGHT_GRAY: Color32 = Color32::from_rgb(220, 220, 220); + pub const WHITE: Color32 = Color32::from_rgb(255, 255, 255); + + pub const BROWN: Color32 = Color32::from_rgb(165, 42, 42); + pub const DARK_RED: Color32 = Color32::from_rgb(0x8B, 0, 0); + pub const RED: Color32 = Color32::from_rgb(255, 0, 0); + pub const LIGHT_RED: Color32 = Color32::from_rgb(255, 128, 128); + + pub const YELLOW: Color32 = Color32::from_rgb(255, 255, 0); + pub const LIGHT_YELLOW: Color32 = Color32::from_rgb(255, 255, 0xE0); + pub const KHAKI: Color32 = Color32::from_rgb(240, 230, 140); + + pub const DARK_GREEN: Color32 = Color32::from_rgb(0, 0x64, 0); + pub const GREEN: Color32 = Color32::from_rgb(0, 255, 0); + pub const LIGHT_GREEN: Color32 = Color32::from_rgb(0x90, 0xEE, 0x90); + + pub const DARK_BLUE: Color32 = Color32::from_rgb(0, 0, 0x8B); + pub const BLUE: Color32 = Color32::from_rgb(0, 0, 255); + pub const LIGHT_BLUE: Color32 = Color32::from_rgb(0xAD, 0xD8, 0xE6); + + pub const GOLD: Color32 = Color32::from_rgb(255, 215, 0); + + pub const DEBUG_COLOR: Color32 = Color32::from_rgba_premultiplied(0, 200, 0, 128); + + /// An ugly color that is planned to be replaced before making it to the screen. + pub const TEMPORARY_COLOR: Color32 = Color32::from_rgb(64, 254, 0); + + #[inline(always)] + pub const fn from_rgb(r: u8, g: u8, b: u8) -> Self { + Self([r, g, b, 255]) + } + + #[inline(always)] + pub const fn from_rgb_additive(r: u8, g: u8, b: u8) -> Self { + Self([r, g, b, 0]) + } + + /// From `sRGBA` with premultiplied alpha. + #[inline(always)] + pub const fn from_rgba_premultiplied(r: u8, g: u8, b: u8, a: u8) -> Self { + Self([r, g, b, a]) + } + + /// From `sRGBA` WITHOUT premultiplied alpha. + pub fn from_rgba_unmultiplied(r: u8, g: u8, b: u8, a: u8) -> Self { + if a == 255 { + Self::from_rgb(r, g, b) // common-case optimization + } else if a == 0 { + Self::TRANSPARENT // common-case optimization + } else { + let r_lin = linear_f32_from_gamma_u8(r); + let g_lin = linear_f32_from_gamma_u8(g); + let b_lin = linear_f32_from_gamma_u8(b); + let a_lin = linear_f32_from_linear_u8(a); + + let r = gamma_u8_from_linear_f32(r_lin * a_lin); + let g = gamma_u8_from_linear_f32(g_lin * a_lin); + let b = gamma_u8_from_linear_f32(b_lin * a_lin); + + Self::from_rgba_premultiplied(r, g, b, a) + } + } + + #[inline(always)] + pub const fn from_gray(l: u8) -> Self { + Self([l, l, l, 255]) + } + + #[inline(always)] + pub const fn from_black_alpha(a: u8) -> Self { + Self([0, 0, 0, a]) + } + + pub fn from_white_alpha(a: u8) -> Self { + Rgba::from_white_alpha(linear_f32_from_linear_u8(a)).into() + } + + #[inline(always)] + pub const fn from_additive_luminance(l: u8) -> Self { + Self([l, l, l, 0]) + } + + #[inline(always)] + pub const fn is_opaque(&self) -> bool { + self.a() == 255 + } + + #[inline(always)] + pub const fn r(&self) -> u8 { + self.0[0] + } + + #[inline(always)] + pub const fn g(&self) -> u8 { + self.0[1] + } + + #[inline(always)] + pub const fn b(&self) -> u8 { + self.0[2] + } + + #[inline(always)] + pub const fn a(&self) -> u8 { + self.0[3] + } + + /// Returns an opaque version of self + pub fn to_opaque(self) -> Self { + Rgba::from(self).to_opaque().into() + } + + /// Returns an additive version of self + #[inline(always)] + pub const fn additive(self) -> Self { + let [r, g, b, _] = self.to_array(); + Self([r, g, b, 0]) + } + + /// Premultiplied RGBA + #[inline(always)] + pub const fn to_array(&self) -> [u8; 4] { + [self.r(), self.g(), self.b(), self.a()] + } + + /// Premultiplied RGBA + #[inline(always)] + pub const fn to_tuple(&self) -> (u8, u8, u8, u8) { + (self.r(), self.g(), self.b(), self.a()) + } + + pub fn to_srgba_unmultiplied(&self) -> [u8; 4] { + Rgba::from(*self).to_srgba_unmultiplied() + } + + /// Multiply with 0.5 to make color half as opaque, perceptually. + /// + /// Fast multiplication in gamma-space. + /// + /// This is perceptually even, and faster that [`Self::linear_multiply`]. + #[inline] + pub fn gamma_multiply(self, factor: f32) -> Color32 { + crate::ecolor_assert!(0.0 <= factor && factor <= 1.0); + let Self([r, g, b, a]) = self; + Self([ + (r as f32 * factor + 0.5) as u8, + (g as f32 * factor + 0.5) as u8, + (b as f32 * factor + 0.5) as u8, + (a as f32 * factor + 0.5) as u8, + ]) + } + + /// Multiply with 0.5 to make color half as opaque in linear space. + /// + /// This is using linear space, which is not perceptually even. + /// You may want to use [`Self::gamma_multiply`] instead. + pub fn linear_multiply(self, factor: f32) -> Color32 { + crate::ecolor_assert!(0.0 <= factor && factor <= 1.0); + // As an unfortunate side-effect of using premultiplied alpha + // we need a somewhat expensive conversion to linear space and back. + Rgba::from(self).multiply(factor).into() + } + + /// Converts to floating point values in the range 0-1 without any gamma space conversion. + /// + /// Use this with great care! In almost all cases, you want to convert to [`crate::Rgba`] instead + /// in order to obtain linear space color values. + #[inline] + pub fn to_normalized_gamma_f32(self) -> [f32; 4] { + let Self([r, g, b, a]) = self; + [ + r as f32 / 255.0, + g as f32 / 255.0, + b as f32 / 255.0, + a as f32 / 255.0, + ] + } +} diff --git a/nevmes-gui/crates/ecolor/src/hex_color_macro.rs b/nevmes-gui/crates/ecolor/src/hex_color_macro.rs new file mode 100644 index 0000000..450e6a8 --- /dev/null +++ b/nevmes-gui/crates/ecolor/src/hex_color_macro.rs @@ -0,0 +1,39 @@ +/// Construct a [`crate::Color32`] from a hex RGB or RGBA string. +/// +/// ``` +/// # use ecolor::{hex_color, Color32}; +/// assert_eq!(hex_color!("#202122"), Color32::from_rgb(0x20, 0x21, 0x22)); +/// assert_eq!(hex_color!("#abcdef12"), Color32::from_rgba_unmultiplied(0xab, 0xcd, 0xef, 0x12)); +/// ``` +#[macro_export] +macro_rules! hex_color { + ($s:literal) => {{ + let array = color_hex::color_from_hex!($s); + if array.len() == 3 { + $crate::Color32::from_rgb(array[0], array[1], array[2]) + } else { + #[allow(unconditional_panic)] + $crate::Color32::from_rgba_unmultiplied(array[0], array[1], array[2], array[3]) + } + }}; +} + +#[test] +fn test_from_rgb_hex() { + assert_eq!( + crate::Color32::from_rgb(0x20, 0x21, 0x22), + hex_color!("#202122") + ); + assert_eq!( + crate::Color32::from_rgb_additive(0x20, 0x21, 0x22), + hex_color!("#202122").additive() + ); +} + +#[test] +fn test_from_rgba_hex() { + assert_eq!( + crate::Color32::from_rgba_unmultiplied(0x20, 0x21, 0x22, 0x50), + hex_color!("20212250") + ); +} diff --git a/nevmes-gui/crates/ecolor/src/hsva.rs b/nevmes-gui/crates/ecolor/src/hsva.rs new file mode 100644 index 0000000..8a68cb9 --- /dev/null +++ b/nevmes-gui/crates/ecolor/src/hsva.rs @@ -0,0 +1,231 @@ +use crate::{ + gamma_u8_from_linear_f32, linear_f32_from_gamma_u8, linear_f32_from_linear_u8, + linear_u8_from_linear_f32, Color32, Rgba, +}; + +/// Hue, saturation, value, alpha. All in the range [0, 1]. +/// No premultiplied alpha. +#[derive(Clone, Copy, Debug, Default, PartialEq)] +pub struct Hsva { + /// hue 0-1 + pub h: f32, + + /// saturation 0-1 + pub s: f32, + + /// value 0-1 + pub v: f32, + + /// alpha 0-1. A negative value signifies an additive color (and alpha is ignored). + pub a: f32, +} + +impl Hsva { + pub fn new(h: f32, s: f32, v: f32, a: f32) -> Self { + Self { h, s, v, a } + } + + /// From `sRGBA` with premultiplied alpha + pub fn from_srgba_premultiplied(srgba: [u8; 4]) -> Self { + Self::from_rgba_premultiplied( + linear_f32_from_gamma_u8(srgba[0]), + linear_f32_from_gamma_u8(srgba[1]), + linear_f32_from_gamma_u8(srgba[2]), + linear_f32_from_linear_u8(srgba[3]), + ) + } + + /// From `sRGBA` without premultiplied alpha + pub fn from_srgba_unmultiplied(srgba: [u8; 4]) -> Self { + Self::from_rgba_unmultiplied( + linear_f32_from_gamma_u8(srgba[0]), + linear_f32_from_gamma_u8(srgba[1]), + linear_f32_from_gamma_u8(srgba[2]), + linear_f32_from_linear_u8(srgba[3]), + ) + } + + /// From linear RGBA with premultiplied alpha + pub fn from_rgba_premultiplied(r: f32, g: f32, b: f32, a: f32) -> Self { + #![allow(clippy::many_single_char_names)] + if a == 0.0 { + if r == 0.0 && b == 0.0 && a == 0.0 { + Hsva::default() + } else { + Hsva::from_additive_rgb([r, g, b]) + } + } else { + let (h, s, v) = hsv_from_rgb([r / a, g / a, b / a]); + Hsva { h, s, v, a } + } + } + + /// From linear RGBA without premultiplied alpha + pub fn from_rgba_unmultiplied(r: f32, g: f32, b: f32, a: f32) -> Self { + #![allow(clippy::many_single_char_names)] + let (h, s, v) = hsv_from_rgb([r, g, b]); + Hsva { h, s, v, a } + } + + pub fn from_additive_rgb(rgb: [f32; 3]) -> Self { + let (h, s, v) = hsv_from_rgb(rgb); + Hsva { + h, + s, + v, + a: -0.5, // anything negative is treated as additive + } + } + + pub fn from_rgb(rgb: [f32; 3]) -> Self { + let (h, s, v) = hsv_from_rgb(rgb); + Hsva { h, s, v, a: 1.0 } + } + + pub fn from_srgb([r, g, b]: [u8; 3]) -> Self { + Self::from_rgb([ + linear_f32_from_gamma_u8(r), + linear_f32_from_gamma_u8(g), + linear_f32_from_gamma_u8(b), + ]) + } + + // ------------------------------------------------------------------------ + + pub fn to_opaque(self) -> Self { + Self { a: 1.0, ..self } + } + + pub fn to_rgb(&self) -> [f32; 3] { + rgb_from_hsv((self.h, self.s, self.v)) + } + + pub fn to_srgb(&self) -> [u8; 3] { + let [r, g, b] = self.to_rgb(); + [ + gamma_u8_from_linear_f32(r), + gamma_u8_from_linear_f32(g), + gamma_u8_from_linear_f32(b), + ] + } + + pub fn to_rgba_premultiplied(&self) -> [f32; 4] { + let [r, g, b, a] = self.to_rgba_unmultiplied(); + let additive = a < 0.0; + if additive { + [r, g, b, 0.0] + } else { + [a * r, a * g, a * b, a] + } + } + + /// Represents additive colors using a negative alpha. + pub fn to_rgba_unmultiplied(&self) -> [f32; 4] { + let Hsva { h, s, v, a } = *self; + let [r, g, b] = rgb_from_hsv((h, s, v)); + [r, g, b, a] + } + + pub fn to_srgba_premultiplied(&self) -> [u8; 4] { + let [r, g, b, a] = self.to_rgba_premultiplied(); + [ + gamma_u8_from_linear_f32(r), + gamma_u8_from_linear_f32(g), + gamma_u8_from_linear_f32(b), + linear_u8_from_linear_f32(a), + ] + } + + pub fn to_srgba_unmultiplied(&self) -> [u8; 4] { + let [r, g, b, a] = self.to_rgba_unmultiplied(); + [ + gamma_u8_from_linear_f32(r), + gamma_u8_from_linear_f32(g), + gamma_u8_from_linear_f32(b), + linear_u8_from_linear_f32(a.abs()), + ] + } +} + +impl From for Rgba { + fn from(hsva: Hsva) -> Rgba { + Rgba(hsva.to_rgba_premultiplied()) + } +} + +impl From for Hsva { + fn from(rgba: Rgba) -> Hsva { + Self::from_rgba_premultiplied(rgba.0[0], rgba.0[1], rgba.0[2], rgba.0[3]) + } +} + +impl From for Color32 { + fn from(hsva: Hsva) -> Color32 { + Color32::from(Rgba::from(hsva)) + } +} + +impl From for Hsva { + fn from(srgba: Color32) -> Hsva { + Hsva::from(Rgba::from(srgba)) + } +} + +/// All ranges in 0-1, rgb is linear. +pub fn hsv_from_rgb([r, g, b]: [f32; 3]) -> (f32, f32, f32) { + #![allow(clippy::many_single_char_names)] + let min = r.min(g.min(b)); + let max = r.max(g.max(b)); // value + + let range = max - min; + + let h = if max == min { + 0.0 // hue is undefined + } else if max == r { + (g - b) / (6.0 * range) + } else if max == g { + (b - r) / (6.0 * range) + 1.0 / 3.0 + } else { + // max == b + (r - g) / (6.0 * range) + 2.0 / 3.0 + }; + let h = (h + 1.0).fract(); // wrap + let s = if max == 0.0 { 0.0 } else { 1.0 - min / max }; + (h, s, max) +} + +/// All ranges in 0-1, rgb is linear. +pub fn rgb_from_hsv((h, s, v): (f32, f32, f32)) -> [f32; 3] { + #![allow(clippy::many_single_char_names)] + let h = (h.fract() + 1.0).fract(); // wrap + let s = s.clamp(0.0, 1.0); + + let f = h * 6.0 - (h * 6.0).floor(); + let p = v * (1.0 - s); + let q = v * (1.0 - f * s); + let t = v * (1.0 - (1.0 - f) * s); + + match (h * 6.0).floor() as i32 % 6 { + 0 => [v, t, p], + 1 => [q, v, p], + 2 => [p, v, t], + 3 => [p, q, v], + 4 => [t, p, v], + 5 => [v, p, q], + _ => unreachable!(), + } +} + +#[test] +#[ignore] // a bit expensive +fn test_hsv_roundtrip() { + for r in 0..=255 { + for g in 0..=255 { + for b in 0..=255 { + let srgba = Color32::from_rgb(r, g, b); + let hsva = Hsva::from(srgba); + assert_eq!(srgba, Color32::from(hsva)); + } + } + } +} diff --git a/nevmes-gui/crates/ecolor/src/hsva_gamma.rs b/nevmes-gui/crates/ecolor/src/hsva_gamma.rs new file mode 100644 index 0000000..3135ef1 --- /dev/null +++ b/nevmes-gui/crates/ecolor/src/hsva_gamma.rs @@ -0,0 +1,66 @@ +use crate::{gamma_from_linear, linear_from_gamma, Color32, Hsva, Rgba}; + +/// Like Hsva but with the `v` value (brightness) being gamma corrected +/// so that it is somewhat perceptually even. +#[derive(Clone, Copy, Debug, Default, PartialEq)] +pub struct HsvaGamma { + /// hue 0-1 + pub h: f32, + + /// saturation 0-1 + pub s: f32, + + /// value 0-1, in gamma-space (~perceptually even) + pub v: f32, + + /// alpha 0-1. A negative value signifies an additive color (and alpha is ignored). + pub a: f32, +} + +impl From for Rgba { + fn from(hsvag: HsvaGamma) -> Rgba { + Hsva::from(hsvag).into() + } +} + +impl From for Color32 { + fn from(hsvag: HsvaGamma) -> Color32 { + Rgba::from(hsvag).into() + } +} + +impl From for Hsva { + fn from(hsvag: HsvaGamma) -> Hsva { + let HsvaGamma { h, s, v, a } = hsvag; + Hsva { + h, + s, + v: linear_from_gamma(v), + a, + } + } +} + +impl From for HsvaGamma { + fn from(rgba: Rgba) -> HsvaGamma { + Hsva::from(rgba).into() + } +} + +impl From for HsvaGamma { + fn from(srgba: Color32) -> HsvaGamma { + Hsva::from(srgba).into() + } +} + +impl From for HsvaGamma { + fn from(hsva: Hsva) -> HsvaGamma { + let Hsva { h, s, v, a } = hsva; + HsvaGamma { + h, + s, + v: gamma_from_linear(v), + a, + } + } +} diff --git a/nevmes-gui/crates/ecolor/src/lib.rs b/nevmes-gui/crates/ecolor/src/lib.rs new file mode 100644 index 0000000..9bc42c4 --- /dev/null +++ b/nevmes-gui/crates/ecolor/src/lib.rs @@ -0,0 +1,173 @@ +//! Color conversions and types. +//! +//! If you want a compact color representation, use [`Color32`]. +//! If you want to manipulate RGBA colors use [`Rgba`]. +//! If you want to manipulate colors in a way closer to how humans think about colors, use [`HsvaGamma`]. +//! +//! ## Feature flags +#![cfg_attr(feature = "document-features", doc = document_features::document_features!())] +//! + +#![allow(clippy::wrong_self_convention)] + +#[cfg(feature = "cint")] +mod cint_impl; +#[cfg(feature = "cint")] +pub use cint_impl::*; + +mod color32; +pub use color32::*; + +mod hsva_gamma; +pub use hsva_gamma::*; + +mod hsva; +pub use hsva::*; + +#[cfg(feature = "color-hex")] +mod hex_color_macro; + +mod rgba; +pub use rgba::*; + +// ---------------------------------------------------------------------------- +// Color conversion: + +impl From for Rgba { + fn from(srgba: Color32) -> Rgba { + Rgba([ + linear_f32_from_gamma_u8(srgba.0[0]), + linear_f32_from_gamma_u8(srgba.0[1]), + linear_f32_from_gamma_u8(srgba.0[2]), + linear_f32_from_linear_u8(srgba.0[3]), + ]) + } +} + +impl From for Color32 { + fn from(rgba: Rgba) -> Color32 { + Color32([ + gamma_u8_from_linear_f32(rgba.0[0]), + gamma_u8_from_linear_f32(rgba.0[1]), + gamma_u8_from_linear_f32(rgba.0[2]), + linear_u8_from_linear_f32(rgba.0[3]), + ]) + } +} + +/// gamma [0, 255] -> linear [0, 1]. +pub fn linear_f32_from_gamma_u8(s: u8) -> f32 { + if s <= 10 { + s as f32 / 3294.6 + } else { + ((s as f32 + 14.025) / 269.025).powf(2.4) + } +} + +/// linear [0, 255] -> linear [0, 1]. +/// Useful for alpha-channel. +#[inline(always)] +pub fn linear_f32_from_linear_u8(a: u8) -> f32 { + a as f32 / 255.0 +} + +/// linear [0, 1] -> gamma [0, 255] (clamped). +/// Values outside this range will be clamped to the range. +pub fn gamma_u8_from_linear_f32(l: f32) -> u8 { + if l <= 0.0 { + 0 + } else if l <= 0.0031308 { + fast_round(3294.6 * l) + } else if l <= 1.0 { + fast_round(269.025 * l.powf(1.0 / 2.4) - 14.025) + } else { + 255 + } +} + +/// linear [0, 1] -> linear [0, 255] (clamped). +/// Useful for alpha-channel. +#[inline(always)] +pub fn linear_u8_from_linear_f32(a: f32) -> u8 { + fast_round(a * 255.0) +} + +fn fast_round(r: f32) -> u8 { + (r + 0.5).floor() as _ // rust does a saturating cast since 1.45 +} + +#[test] +pub fn test_srgba_conversion() { + for b in 0..=255 { + let l = linear_f32_from_gamma_u8(b); + assert!(0.0 <= l && l <= 1.0); + assert_eq!(gamma_u8_from_linear_f32(l), b); + } +} + +/// gamma [0, 1] -> linear [0, 1] (not clamped). +/// Works for numbers outside this range (e.g. negative numbers). +pub fn linear_from_gamma(gamma: f32) -> f32 { + if gamma < 0.0 { + -linear_from_gamma(-gamma) + } else if gamma <= 0.04045 { + gamma / 12.92 + } else { + ((gamma + 0.055) / 1.055).powf(2.4) + } +} + +/// linear [0, 1] -> gamma [0, 1] (not clamped). +/// Works for numbers outside this range (e.g. negative numbers). +pub fn gamma_from_linear(linear: f32) -> f32 { + if linear < 0.0 { + -gamma_from_linear(-linear) + } else if linear <= 0.0031308 { + 12.92 * linear + } else { + 1.055 * linear.powf(1.0 / 2.4) - 0.055 + } +} + +// ---------------------------------------------------------------------------- + +/// An assert that is only active when `epaint` is compiled with the `extra_asserts` feature +/// or with the `extra_debug_asserts` feature in debug builds. +#[macro_export] +macro_rules! ecolor_assert { + ($($arg: tt)*) => { + if cfg!(any( + feature = "extra_asserts", + all(feature = "extra_debug_asserts", debug_assertions), + )) { + assert!($($arg)*); + } + } +} + +// ---------------------------------------------------------------------------- + +/// Cheap and ugly. +/// Made for graying out disabled `Ui`s. +pub fn tint_color_towards(color: Color32, target: Color32) -> Color32 { + let [mut r, mut g, mut b, mut a] = color.to_array(); + + if a == 0 { + r /= 2; + g /= 2; + b /= 2; + } else if a < 170 { + // Cheapish and looks ok. + // Works for e.g. grid stripes. + let div = (2 * 255 / a as i32) as u8; + r = r / 2 + target.r() / div; + g = g / 2 + target.g() / div; + b = b / 2 + target.b() / div; + a /= 2; + } else { + r = r / 2 + target.r() / 2; + g = g / 2 + target.g() / 2; + b = b / 2 + target.b() / 2; + } + Color32::from_rgba_premultiplied(r, g, b, a) +} diff --git a/nevmes-gui/crates/ecolor/src/rgba.rs b/nevmes-gui/crates/ecolor/src/rgba.rs new file mode 100644 index 0000000..f9d671f --- /dev/null +++ b/nevmes-gui/crates/ecolor/src/rgba.rs @@ -0,0 +1,266 @@ +use crate::{ + gamma_u8_from_linear_f32, linear_f32_from_gamma_u8, linear_f32_from_linear_u8, + linear_u8_from_linear_f32, +}; + +/// 0-1 linear space `RGBA` color with premultiplied alpha. +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))] +pub struct Rgba(pub(crate) [f32; 4]); + +impl std::ops::Index for Rgba { + type Output = f32; + + #[inline(always)] + fn index(&self, index: usize) -> &f32 { + &self.0[index] + } +} + +impl std::ops::IndexMut for Rgba { + #[inline(always)] + fn index_mut(&mut self, index: usize) -> &mut f32 { + &mut self.0[index] + } +} + +#[inline(always)] +pub(crate) fn f32_hash(state: &mut H, f: f32) { + if f == 0.0 { + state.write_u8(0); + } else if f.is_nan() { + state.write_u8(1); + } else { + use std::hash::Hash; + f.to_bits().hash(state); + } +} + +#[allow(clippy::derive_hash_xor_eq)] +impl std::hash::Hash for Rgba { + #[inline] + fn hash(&self, state: &mut H) { + crate::f32_hash(state, self.0[0]); + crate::f32_hash(state, self.0[1]); + crate::f32_hash(state, self.0[2]); + crate::f32_hash(state, self.0[3]); + } +} + +impl Rgba { + pub const TRANSPARENT: Rgba = Rgba::from_rgba_premultiplied(0.0, 0.0, 0.0, 0.0); + pub const BLACK: Rgba = Rgba::from_rgb(0.0, 0.0, 0.0); + pub const WHITE: Rgba = Rgba::from_rgb(1.0, 1.0, 1.0); + pub const RED: Rgba = Rgba::from_rgb(1.0, 0.0, 0.0); + pub const GREEN: Rgba = Rgba::from_rgb(0.0, 1.0, 0.0); + pub const BLUE: Rgba = Rgba::from_rgb(0.0, 0.0, 1.0); + + #[inline(always)] + pub const fn from_rgba_premultiplied(r: f32, g: f32, b: f32, a: f32) -> Self { + Self([r, g, b, a]) + } + + #[inline(always)] + pub fn from_rgba_unmultiplied(r: f32, g: f32, b: f32, a: f32) -> Self { + Self([r * a, g * a, b * a, a]) + } + + #[inline(always)] + pub fn from_srgba_premultiplied(r: u8, g: u8, b: u8, a: u8) -> Self { + let r = linear_f32_from_gamma_u8(r); + let g = linear_f32_from_gamma_u8(g); + let b = linear_f32_from_gamma_u8(b); + let a = linear_f32_from_linear_u8(a); + Self::from_rgba_premultiplied(r, g, b, a) + } + + #[inline(always)] + pub fn from_srgba_unmultiplied(r: u8, g: u8, b: u8, a: u8) -> Self { + let r = linear_f32_from_gamma_u8(r); + let g = linear_f32_from_gamma_u8(g); + let b = linear_f32_from_gamma_u8(b); + let a = linear_f32_from_linear_u8(a); + Self::from_rgba_premultiplied(r * a, g * a, b * a, a) + } + + #[inline(always)] + pub const fn from_rgb(r: f32, g: f32, b: f32) -> Self { + Self([r, g, b, 1.0]) + } + + #[inline(always)] + pub const fn from_gray(l: f32) -> Self { + Self([l, l, l, 1.0]) + } + + pub fn from_luminance_alpha(l: f32, a: f32) -> Self { + crate::ecolor_assert!(0.0 <= l && l <= 1.0); + crate::ecolor_assert!(0.0 <= a && a <= 1.0); + Self([l * a, l * a, l * a, a]) + } + + /// Transparent black + #[inline(always)] + pub fn from_black_alpha(a: f32) -> Self { + crate::ecolor_assert!(0.0 <= a && a <= 1.0); + Self([0.0, 0.0, 0.0, a]) + } + + /// Transparent white + #[inline(always)] + pub fn from_white_alpha(a: f32) -> Self { + crate::ecolor_assert!(0.0 <= a && a <= 1.0, "a: {}", a); + Self([a, a, a, a]) + } + + /// Return an additive version of this color (alpha = 0) + #[inline(always)] + pub fn additive(self) -> Self { + let [r, g, b, _] = self.0; + Self([r, g, b, 0.0]) + } + + /// Multiply with e.g. 0.5 to make us half transparent + #[inline(always)] + pub fn multiply(self, alpha: f32) -> Self { + Self([ + alpha * self[0], + alpha * self[1], + alpha * self[2], + alpha * self[3], + ]) + } + + #[inline(always)] + pub fn r(&self) -> f32 { + self.0[0] + } + + #[inline(always)] + pub fn g(&self) -> f32 { + self.0[1] + } + + #[inline(always)] + pub fn b(&self) -> f32 { + self.0[2] + } + + #[inline(always)] + pub fn a(&self) -> f32 { + self.0[3] + } + + /// How perceptually intense (bright) is the color? + #[inline] + pub fn intensity(&self) -> f32 { + 0.3 * self.r() + 0.59 * self.g() + 0.11 * self.b() + } + + /// Returns an opaque version of self + pub fn to_opaque(&self) -> Self { + if self.a() == 0.0 { + // Additive or fully transparent black. + Self::from_rgb(self.r(), self.g(), self.b()) + } else { + // un-multiply alpha: + Self::from_rgb( + self.r() / self.a(), + self.g() / self.a(), + self.b() / self.a(), + ) + } + } + + /// Premultiplied RGBA + #[inline(always)] + pub fn to_array(&self) -> [f32; 4] { + [self.r(), self.g(), self.b(), self.a()] + } + + /// Premultiplied RGBA + #[inline(always)] + pub fn to_tuple(&self) -> (f32, f32, f32, f32) { + (self.r(), self.g(), self.b(), self.a()) + } + + /// unmultiply the alpha + pub fn to_rgba_unmultiplied(&self) -> [f32; 4] { + let a = self.a(); + if a == 0.0 { + // Additive, let's assume we are black + self.0 + } else { + [self.r() / a, self.g() / a, self.b() / a, a] + } + } + + /// unmultiply the alpha + pub fn to_srgba_unmultiplied(&self) -> [u8; 4] { + let [r, g, b, a] = self.to_rgba_unmultiplied(); + [ + gamma_u8_from_linear_f32(r), + gamma_u8_from_linear_f32(g), + gamma_u8_from_linear_f32(b), + linear_u8_from_linear_f32(a.abs()), + ] + } +} + +impl std::ops::Add for Rgba { + type Output = Rgba; + + #[inline(always)] + fn add(self, rhs: Rgba) -> Rgba { + Rgba([ + self[0] + rhs[0], + self[1] + rhs[1], + self[2] + rhs[2], + self[3] + rhs[3], + ]) + } +} + +impl std::ops::Mul for Rgba { + type Output = Rgba; + + #[inline(always)] + fn mul(self, other: Rgba) -> Rgba { + Rgba([ + self[0] * other[0], + self[1] * other[1], + self[2] * other[2], + self[3] * other[3], + ]) + } +} + +impl std::ops::Mul for Rgba { + type Output = Rgba; + + #[inline(always)] + fn mul(self, factor: f32) -> Rgba { + Rgba([ + self[0] * factor, + self[1] * factor, + self[2] * factor, + self[3] * factor, + ]) + } +} + +impl std::ops::Mul for f32 { + type Output = Rgba; + + #[inline(always)] + fn mul(self, rgba: Rgba) -> Rgba { + Rgba([ + self * rgba[0], + self * rgba[1], + self * rgba[2], + self * rgba[3], + ]) + } +} diff --git a/nevmes-gui/crates/eframe/CHANGELOG.md b/nevmes-gui/crates/eframe/CHANGELOG.md new file mode 100644 index 0000000..4f5e50c --- /dev/null +++ b/nevmes-gui/crates/eframe/CHANGELOG.md @@ -0,0 +1,229 @@ +# Changelog for eframe +All notable changes to the `eframe` crate. + +NOTE: [`egui-winit`](../egui-winit/CHANGELOG.md), [`egui_glium`](../egui_glium/CHANGELOG.md), [`egui_glow`](../egui_glow/CHANGELOG.md),and [`egui-wgpu`](../egui-wgpu/CHANGELOG.md) have their own changelogs! + + +## Unreleased + + +## 0.21.3 - 2023-02-15 +* Fix typing the letter 'P' on web ([#2740](https://github.com/emilk/egui/pull/2740)). + + +## 0.21.2 - 2023-02-12 +* Allow compiling `eframe` with `--no-default-features` ([#2728](https://github.com/emilk/egui/pull/2728)). + + +## 0.21.1 - 2023-02-12 +* Fixed crash when native window position is in an invalid state, which could happen e.g. due to changes in monitor size or DPI ([#2722](https://github.com/emilk/egui/issues/2722)). + + +## 0.21.0 - 2023-02-08 - Update to `winit` 0.28 +* ⚠️ BREAKING: `App::clear_color` now expects you to return a raw float array ([#2666](https://github.com/emilk/egui/pull/2666)). +* The `screen_reader` feature has now been renamed `web_screen_reader` and only work on web. On other platforms, use the `accesskit` feature flag instead ([#2669](https://github.com/emilk/egui/pull/2669)). + +#### Desktop/Native: +* `eframe::run_native` now returns a `Result` ([#2433](https://github.com/emilk/egui/pull/2433)). +* Update to `winit` 0.28, adding support for mac trackpad zoom ([#2654](https://github.com/emilk/egui/pull/2654)). +* Fix bug where the cursor could get stuck using the wrong icon. +* `NativeOptions::transparent` now works with the wgpu backend ([#2684](https://github.com/emilk/egui/pull/2684)). +* Add `Frame::set_minimized` and `set_maximized` ([#2292](https://github.com/emilk/egui/pull/2292), [#2672](https://github.com/emilk/egui/pull/2672)). +* Fixed persistence of native window position on Windows OS ([#2583](https://github.com/emilk/egui/issues/2583)). + +#### Web: +* Prevent ctrl-P/cmd-P from opening the print dialog ([#2598](https://github.com/emilk/egui/pull/2598)). + + +## 0.20.1 - 2022-12-11 +* Fix [docs.rs](https://docs.rs/eframe) build ([#2420](https://github.com/emilk/egui/pull/2420)). + + +## 0.20.0 - 2022-12-08 - AccessKit integration and `wgpu` web support +* MSRV (Minimum Supported Rust Version) is now `1.65.0` ([#2314](https://github.com/emilk/egui/pull/2314)). +* Allow empty textures with the glow renderer. + +#### Desktop/Native: +* Don't repaint when just moving window ([#1980](https://github.com/emilk/egui/pull/1980)). +* Added `NativeOptions::event_loop_builder` hook for apps to change platform specific event loop options ([#1952](https://github.com/emilk/egui/pull/1952)). +* Enabled deferred render state initialization to support Android ([#1952](https://github.com/emilk/egui/pull/1952)). +* Added `shader_version` to `NativeOptions` for cross compiling support on different target OpenGL | ES versions (on native `glow` renderer only) ([#1993](https://github.com/emilk/egui/pull/1993)). +* Fix: app state is now saved when user presses Cmd-Q on Mac ([#2013](https://github.com/emilk/egui/pull/2013)). +* Added `center` to `NativeOptions` and `monitor_size` to `WindowInfo` on desktop ([#2035](https://github.com/emilk/egui/pull/2035)). +* Improve IME support ([#2046](https://github.com/emilk/egui/pull/2046)). +* Added mouse-passthrough option ([#2080](https://github.com/emilk/egui/pull/2080)). +* Added `NativeOptions::fullsize_content` option on Mac to build titlebar-less windows with floating window controls ([#2049](https://github.com/emilk/egui/pull/2049)). +* Wgpu device/adapter/surface creation has now various configuration options exposed via `NativeOptions/WebOptions::wgpu_options` ([#2207](https://github.com/emilk/egui/pull/2207)). +* Fix: Make sure that `native_pixels_per_point` is updated ([#2256](https://github.com/emilk/egui/pull/2256)). +* Added optional, but enabled by default, integration with [AccessKit](https://accesskit.dev/) for implementing platform accessibility APIs ([#2294](https://github.com/emilk/egui/pull/2294)). +* Fix: Less flickering on resize on Windows ([#2280](https://github.com/emilk/egui/pull/2280)). + +#### Web: +* ⚠️ BREAKING: `start_web` is a now `async` ([#2107](https://github.com/emilk/egui/pull/2107)). +* Web: You can now use WebGL on top of `wgpu` by enabling the `wgpu` feature (and disabling `glow` via disabling default features) ([#2107](https://github.com/emilk/egui/pull/2107)). +* Web: Add `WebInfo::user_agent` ([#2202](https://github.com/emilk/egui/pull/2202)). +* Web: you can access your application from JS using `AppRunner::app_mut`. See `crates/egui_demo_app/src/lib.rs` ([#1886](https://github.com/emilk/egui/pull/1886)). + + +## 0.19.0 - 2022-08-20 +* MSRV (Minimum Supported Rust Version) is now `1.61.0` ([#1846](https://github.com/emilk/egui/pull/1846)). +* Added `wgpu` rendering backed ([#1564](https://github.com/emilk/egui/pull/1564)): + * Added features `wgpu` and `glow`. + * Added `NativeOptions::renderer` to switch between the rendering backends. +* `egui_glow`: remove calls to `gl.get_error` in release builds to speed up rendering ([#1583](https://github.com/emilk/egui/pull/1583)). +* Added `App::post_rendering` for e.g. reading the framebuffer ([#1591](https://github.com/emilk/egui/pull/1591)). +* Use `Arc` for `glow::Context` instead of `Rc` ([#1640](https://github.com/emilk/egui/pull/1640)). +* Fixed bug where the result returned from `App::on_exit_event` would sometimes be ignored ([#1696](https://github.com/emilk/egui/pull/1696)). +* Added `NativeOptions::follow_system_theme` and `NativeOptions::default_theme` ([#1726](https://github.com/emilk/egui/pull/1726)). +* Selectively expose parts of the API based on target arch (`wasm32` or not) ([#1867](https://github.com/emilk/egui/pull/1867)). + +#### Desktop/Native: +* Fixed clipboard on Wayland ([#1613](https://github.com/emilk/egui/pull/1613)). +* Added ability to read window position and size with `frame.info().window_info` ([#1617](https://github.com/emilk/egui/pull/1617)). +* Allow running on native without hardware accelerated rendering. Change with `NativeOptions::hardware_acceleration` ([#1681](https://github.com/emilk/egui/pull/1681), [#1693](https://github.com/emilk/egui/pull/1693)). +* Fixed window position persistence ([#1745](https://github.com/emilk/egui/pull/1745)). +* Fixed mouse cursor change on Linux ([#1747](https://github.com/emilk/egui/pull/1747)). +* Added `Frame::set_visible` ([#1808](https://github.com/emilk/egui/pull/1808)). +* Added fullscreen support ([#1866](https://github.com/emilk/egui/pull/1866)). +* You can now continue execution after closing the native desktop window ([#1889](https://github.com/emilk/egui/pull/1889)). +* `Frame::quit` has been renamed to `Frame::close` and `App::on_exit_event` is now `App::on_close_event` ([#1943](https://github.com/emilk/egui/pull/1943)). + +#### Web: +* Added ability to stop/re-run web app from JavaScript. ⚠️ You need to update your CSS with `html, body: { height: 100%; width: 100%; }` ([#1803](https://github.com/emilk/egui/pull/1650)). +* Added `WebOptions::follow_system_theme` and `WebOptions::default_theme` ([#1726](https://github.com/emilk/egui/pull/1726)). +* Added option to select WebGL version ([#1803](https://github.com/emilk/egui/pull/1803)). + + +## 0.18.0 - 2022-04-30 +* MSRV (Minimum Supported Rust Version) is now `1.60.0` ([#1467](https://github.com/emilk/egui/pull/1467)). +* Removed `eframe::epi` - everything is now in `eframe` (`eframe::App`, `eframe::Frame` etc) ([#1545](https://github.com/emilk/egui/pull/1545)). +* Removed `Frame::request_repaint` - just call `egui::Context::request_repaint` for the same effect ([#1366](https://github.com/emilk/egui/pull/1366)). +* Changed app creation/setup ([#1363](https://github.com/emilk/egui/pull/1363)): + * Removed `App::setup` and `App::name`. + * Provide `CreationContext` when creating app with egui context, storage, integration info and glow context. + * Change interface of `run_native` and `start_web`. +* Added `Frame::storage()` and `Frame::storage_mut()` ([#1418](https://github.com/emilk/egui/pull/1418)). + * You can now load/save state in `App::update` + * Changed `App::update` to take `&mut Frame` instead of `&Frame`. + * `Frame` is no longer `Clone` or `Sync`. +* Added `glow` (OpenGL) context to `Frame` ([#1425](https://github.com/emilk/egui/pull/1425)). + +#### Desktop/Native: +* Remove the `egui_glium` feature. `eframe` will now always use `egui_glow` as the native backend ([#1357](https://github.com/emilk/egui/pull/1357)). +* Change default for `NativeOptions::drag_and_drop_support` to `true` ([#1329](https://github.com/emilk/egui/pull/1329)). +* Added new `NativeOptions`: `vsync`, `multisampling`, `depth_buffer`, `stencil_buffer`. +* `dark-light` (dark mode detection) is now an opt-in feature ([#1437](https://github.com/emilk/egui/pull/1437)). +* Fixed potential scale bug when DPI scaling changes (e.g. when dragging a window between different displays) ([#1441](https://github.com/emilk/egui/pull/1441)). +* Added new feature `puffin` to add [`puffin profiler`](https://github.com/EmbarkStudios/puffin) scopes ([#1483](https://github.com/emilk/egui/pull/1483)). +* Moved app persistence to a background thread, allowing for smoother frame rates (on native). +* Added `Frame::set_window_pos` ([#1505](https://github.com/emilk/egui/pull/1505)). + +#### Web: +* Use full browser width by default ([#1378](https://github.com/emilk/egui/pull/1378)). +* egui code will no longer be called after panic ([#1306](https://github.com/emilk/egui/pull/1306)). + + +## 0.17.0 - 2022-02-22 +* Removed `Frame::alloc_texture`. Use `egui::Context::load_texture` instead ([#1110](https://github.com/emilk/egui/pull/1110)). +* Shift-scroll will now result in horizontal scrolling on all platforms ([#1136](https://github.com/emilk/egui/pull/1136)). +* Log using the `tracing` crate. Log to stdout by adding `tracing_subscriber::fmt::init();` to your `main` ([#1192](https://github.com/emilk/egui/pull/1192)). + +#### Desktop/Native: +* The default native backend is now `egui_glow` (instead of `egui_glium`) ([#1020](https://github.com/emilk/egui/pull/1020)). +* Automatically detect and apply dark or light mode from system ([#1045](https://github.com/emilk/egui/pull/1045)). +* Fixed horizontal scrolling direction on Linux. +* Added `App::on_exit_event` ([#1038](https://github.com/emilk/egui/pull/1038)) +* Added `NativeOptions::initial_window_pos`. +* Fixed `enable_drag` for Windows OS ([#1108](https://github.com/emilk/egui/pull/1108)). + +#### Web: +* The default web painter is now `egui_glow` (instead of WebGL) ([#1020](https://github.com/emilk/egui/pull/1020)). +* Fixed glow failure on Chromium ([#1092](https://github.com/emilk/egui/pull/1092)). +* Updated `eframe::IntegrationInfo::web_location_hash` on `hashchange` event ([#1140](https://github.com/emilk/egui/pull/1140)). +* Expose all parts of the location/url in `frame.info().web_info` ([#1258](https://github.com/emilk/egui/pull/1258)). + + +## 0.16.0 - 2021-12-29 +* `Frame` can now be cloned, saved, and passed to background threads ([#999](https://github.com/emilk/egui/pull/999)). +* Added `Frame::request_repaint` to replace `repaint_signal` ([#999](https://github.com/emilk/egui/pull/999)). +* Added `Frame::alloc_texture/free_texture` to replace `tex_allocator` ([#999](https://github.com/emilk/egui/pull/999)). + +#### Web: +* Fixed [dark rendering in WebKitGTK](https://github.com/emilk/egui/issues/794) ([#888](https://github.com/emilk/egui/pull/888/)). +* Added feature `glow` to switch to a [`glow`](https://github.com/grovesNL/glow) based painter ([#868](https://github.com/emilk/egui/pull/868)). + + +## 0.15.0 - 2021-10-24 +* `Frame` now provides `set_window_title` to set window title dynamically ([#828](https://github.com/emilk/egui/pull/828)). +* `Frame` now provides `set_decorations` to set whether to show window decorations. +* Remove "http" feature (use https://github.com/emilk/ehttp instead!). +* Added `App::persist_native_window` and `App::persist_egui_memory` to control what gets persisted. + +#### Desktop/Native: +* Increase native scroll speed. +* Added new backend `egui_glow` as an alternative to `egui_glium`. Enable with `default-features = false, features = ["default_fonts", "egui_glow"]`. + +#### Web: +* Implement `eframe::NativeTexture` trait for the WebGL painter. +* Deprecate `Painter::register_webgl_texture. +* Fixed multiline paste. +* Fixed painting with non-opaque backgrounds. +* Improve text input on mobile and for IME. + + +## 0.14.0 - 2021-08-24 +* Added dragging and dropping files into egui. +* Improve http fetch API. +* `run_native` now returns when the app is closed. +* Web: Made text thicker and less pixelated. + + +## 0.13.1 - 2021-06-24 +* Fixed `http` feature flag and docs + + +## 0.13.0 - 2021-06-24 +* `App::setup` now takes a `Frame` and `Storage` by argument. +* `App::load` has been removed. Implement `App::setup` instead. +* Web: Default to light visuals unless the system reports a preference for dark mode. +* Web: Improve alpha blending, making fonts look much better (especially in light mode) +* Web: Fix double-paste bug + + +## 0.12.0 - 2021-05-10 +* Moved options out of `trait App` into new `NativeOptions`. +* Added option for `always_on_top`. +* Web: Scroll faster when scrolling with mouse wheel. + + +## 0.11.0 - 2021-04-05 +* You can now turn your window transparent with the `App::transparent` option. +* You can now disable window decorations with the `App::decorated` option. +* Web: [Fix mobile and IME text input](https://github.com/emilk/egui/pull/253) +* Web: Hold down a modifier key when clicking a link to open it in a new tab. + +Contributors: [n2](https://github.com/n2) + + +## 0.10.0 - 2021-02-28 +* [You can now set your own app icons](https://github.com/emilk/egui/pull/193). +* You can control the initial size of the native window with `App::initial_window_size`. +* You can control the maximum egui web canvas size with `App::max_size_points`. +* `Frame::tex_allocator()` no longer returns an `Option` (there is always a texture allocator). + + +## 0.9.0 - 2021-02-07 +* [Added support for HTTP body](https://github.com/emilk/egui/pull/139). +* Web: Right-clicks will no longer open browser context menu. +* Web: Fix a bug where one couldn't select items in a combo box on a touch screen. + + +## 0.8.0 - 2021-01-17 +* Simplify `TextureAllocator` interface. +* WebGL2 is now supported, with improved texture sampler. WebGL1 will be used as a fallback. +* Web: Slightly improved alpha-blending (work-around for non-existing linear-space blending). +* Web: Call `prevent_default` for arrow keys when entering text + + +## 0.7.0 - 2021-01-04 +* Initial release of `eframe` diff --git a/nevmes-gui/crates/eframe/Cargo.toml b/nevmes-gui/crates/eframe/Cargo.toml new file mode 100644 index 0000000..0e08e6f --- /dev/null +++ b/nevmes-gui/crates/eframe/Cargo.toml @@ -0,0 +1,169 @@ +[package] +name = "eframe" +version = "0.21.3" +authors = ["Emil Ernerfeldt "] +description = "egui framework - write GUI apps that compiles to web and/or natively" +edition = "2021" +rust-version = "1.65" +homepage = "https://github.com/emilk/egui/tree/master/crates/eframe" +license = "MIT OR Apache-2.0" +readme = "README.md" +repository = "https://github.com/emilk/egui/tree/master/crates/eframe" +categories = ["gui", "game-development"] +keywords = ["egui", "gui", "gamedev"] +include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"] + +[package.metadata.docs.rs] +all-features = true +targets = ["x86_64-unknown-linux-gnu", "wasm32-unknown-unknown"] + +[lib] + + +[features] +default = ["accesskit", "default_fonts", "glow"] + +## Enable platform accessibility API implementations through [AccessKit](https://accesskit.dev/). +accesskit = ["egui/accesskit", "egui-winit/accesskit"] + +## Detect dark mode system preference using [`dark-light`](https://docs.rs/dark-light). +## +## See also [`NativeOptions::follow_system_theme`] and [`NativeOptions::default_theme`]. +dark-light = ["dep:dark-light"] + +## If set, egui will use `include_bytes!` to bundle some fonts. +## If you plan on specifying your own fonts you may disable this feature. +default_fonts = ["egui/default_fonts"] + +## Use [`glow`](https://github.com/grovesNL/glow) for painting, via [`egui_glow`](https://github.com/emilk/egui/tree/master/crates/egui_glow). +glow = ["dep:glow", "dep:egui_glow", "dep:glutin", "dep:glutin-winit"] + +## Enable saving app state to disk. +persistence = [ + "directories-next", + "egui-winit/serde", + "egui/persistence", + "ron", + "serde", +] + +## Enable profiling with the [`puffin`](https://docs.rs/puffin) crate. +## +## Only enabled on native, because of the low resolution (1ms) of time keeping in browsers. +## `eframe` will call `puffin::GlobalProfiler::lock().new_frame()` for you +puffin = ["dep:puffin", "egui_glow?/puffin", "egui-wgpu?/puffin"] + +## Enable screen reader support (requires `ctx.options_mut(|o| o.screen_reader = true);`) on web. +## +## For other platforms, use the "accesskit" feature instead. +web_screen_reader = ["tts"] + +## If set, eframe will look for the env-var `EFRAME_SCREENSHOT_TO` and write a screenshot to that location, and then quit. +## This is used to generate images for the examples. +__screenshot = ["dep:image"] + +## Use [`wgpu`](https://docs.rs/wgpu) for painting (via [`egui-wgpu`](https://github.com/emilk/egui/tree/master/crates/egui-wgpu)). +## This overrides the `glow` feature. +wgpu = ["dep:wgpu", "dep:egui-wgpu", "dep:pollster"] + + +[dependencies] +egui = { version = "0.21.0", path = "../egui", default-features = false, features = [ + "bytemuck", + "tracing", +] } +thiserror = "1.0.37" +tracing = { version = "0.1", default-features = false, features = ["std"] } + +#! ### Optional dependencies +## Enable this when generating docs. +document-features = { version = "0.2", optional = true } + +egui_glow = { version = "0.21.0", path = "../egui_glow", optional = true, default-features = false } +glow = { version = "0.12", optional = true } +ron = { version = "0.8", optional = true, features = ["integer128"] } +serde = { version = "1", optional = true, features = ["derive"] } + +# ------------------------------------------- +# native: +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +egui-winit = { version = "0.21.1", path = "../egui-winit", default-features = false, features = [ + "clipboard", + "links", +] } +raw-window-handle = { version = "0.5.0" } +winit = "0.28.1" + +# optional native: +dark-light = { version = "1.0", optional = true } +directories-next = { version = "2", optional = true } +egui-wgpu = { version = "0.21.0", path = "../egui-wgpu", optional = true, features = [ + "winit", +] } # if wgpu is used, use it with winit +pollster = { version = "0.3", optional = true } # needed for wgpu + +# we can expose these to user so that they can select which backends they want to enable to avoid compiling useless deps. +# this can be done at the same time we expose x11/wayland features of winit crate. +glutin = { version = "0.30", optional = true } +glutin-winit = { version = "0.3.0", optional = true } +image = { version = "0.24", optional = true, default-features = false, features = [ + "png", +] } +puffin = { version = "0.14", optional = true } +wgpu = { version = "0.15.0", optional = true } + +# ------------------------------------------- +# web: +[target.'cfg(target_arch = "wasm32")'.dependencies] +bytemuck = "1.7" +js-sys = "0.3" +percent-encoding = "2.1" +wasm-bindgen = "=0.2.84" +wasm-bindgen-futures = "0.4" +web-sys = { version = "0.3.58", features = [ + "BinaryType", + "Blob", + "Clipboard", + "ClipboardEvent", + "CompositionEvent", + "console", + "CssStyleDeclaration", + "DataTransfer", + "DataTransferItem", + "DataTransferItemList", + "Document", + "DomRect", + "DragEvent", + "Element", + "Event", + "EventListener", + "EventTarget", + "ExtSRgb", + "File", + "FileList", + "FocusEvent", + "HtmlCanvasElement", + "HtmlElement", + "HtmlInputElement", + "InputEvent", + "KeyboardEvent", + "Location", + "MediaQueryList", + "MouseEvent", + "Navigator", + "Performance", + "Storage", + "Touch", + "TouchEvent", + "TouchList", + "WebGl2RenderingContext", + "WebglDebugRendererInfo", + "WebGlRenderingContext", + "WheelEvent", + "Window", +] } + +# optional web: +egui-wgpu = { version = "0.21.0", path = "../egui-wgpu", optional = true } # if wgpu is used, use it without (!) winit +tts = { version = "0.25", optional = true, default-features = false } +wgpu = { version = "0.15.0", optional = true, features = ["webgl"] } diff --git a/nevmes-gui/crates/eframe/README.md b/nevmes-gui/crates/eframe/README.md new file mode 100644 index 0000000..5599394 --- /dev/null +++ b/nevmes-gui/crates/eframe/README.md @@ -0,0 +1,63 @@ +# eframe: the [`egui`](https://github.com/emilk/egui) framework + +[![Latest version](https://img.shields.io/crates/v/eframe.svg)](https://crates.io/crates/eframe) +[![Documentation](https://docs.rs/eframe/badge.svg)](https://docs.rs/eframe) +![MIT](https://img.shields.io/badge/license-MIT-blue.svg) +![Apache](https://img.shields.io/badge/license-Apache-blue.svg) + +`eframe` is the official framework library for writing apps using [`egui`](https://github.com/emilk/egui). The app can be compiled both to run natively (cross platform) or be compiled to a web app (using WASM). + +To get started, see the [examples](https://github.com/emilk/egui/tree/master/examples). +To learn how to set up `eframe` for web and native, go to and follow the instructions there! + +There is also a tutorial video at . + +For how to use `egui`, see [the egui docs](https://docs.rs/egui). + +--- + +`eframe` uses [`egui_glow`](https://github.com/emilk/egui/tree/master/crates/egui_glow) for rendering, and on native it uses [`egui-winit`](https://github.com/emilk/egui/tree/master/crates/egui-winit). + +To use on Linux, first run: + +``` +sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libssl-dev +``` + +You need to either use `edition = "2021"`, or set `resolver = "2"` in the `[workspace]` section of your to-level `Cargo.toml`. See [this link](https://doc.rust-lang.org/edition-guide/rust-2021/default-cargo-resolver.html) for more info. + +You can opt-in to the using [`egui_wgpu`](https://github.com/emilk/egui/tree/master/crates/egui_wgpu) for rendering by enabling the `wgpu` feature and setting `NativeOptions::renderer` to `Renderer::Wgpu`. + + +## Alternatives +`eframe` is not the only way to write an app using `egui`! You can also try [`egui-miniquad`](https://github.com/not-fl3/egui-miniquad), [`bevy_egui`](https://github.com/mvlabat/bevy_egui), [`egui_sdl2_gl`](https://github.com/ArjunNair/egui_sdl2_gl), and others. + +You can also use `egui_glow` and [`winit`](https://github.com/rust-windowing/winit) to build your own app as demonstrated in . + + +## Problems with running egui on the web +`eframe` uses WebGL (via [`glow`](https://crates.io/crates/glow)) and WASM, and almost nothing else from the web tech stack. This has some benefits, but also produces some challenges and serious downsides. + +* Rendering: Getting pixel-perfect rendering right on the web is very difficult. +* Search: you cannot search an egui web page like you would a normal web page. +* Bringing up an on-screen keyboard on mobile: there is no JS function to do this, so `eframe` fakes it by adding some invisible DOM elements. It doesn't always work. +* Mobile text editing is not as good as for a normal web app. +* Accessibility: There is an experimental screen reader for `eframe`, but it has to be enabled explicitly. There is no JS function to ask "Does the user want a screen reader?" (and there should probably not be such a function, due to user tracking/integrity concerns). +* No integration with browser settings for colors and fonts. + +In many ways, `eframe` is trying to make the browser do something it wasn't designed to do (though there are many things browser vendors could do to improve how well libraries like egui work). + +The suggested use for `eframe` are for web apps where performance and responsiveness are more important than accessibility and mobile text editing. + + +## Companion crates +Not all rust crates work when compiled to WASM, but here are some useful crates have been designed to work well both natively and as WASM: + +* Audio: [`cpal`](https://github.com/RustAudio/cpal). +* HTTP client: [`ehttp`](https://github.com/emilk/ehttp) and [`reqwest`](https://github.com/seanmonstar/reqwest). +* Time: [`chrono`](https://github.com/chronotope/chrono). +* WebSockets: [`ewebsock`](https://github.com/rerun-io/ewebsock). + + +## Name +The _frame_ in `eframe` stands both for the frame in which your `egui` app resides and also for "framework" (`frame` is a framework, `egui` is a library). diff --git a/nevmes-gui/crates/eframe/src/epi.rs b/nevmes-gui/crates/eframe/src/epi.rs new file mode 100644 index 0000000..8372d65 --- /dev/null +++ b/nevmes-gui/crates/eframe/src/epi.rs @@ -0,0 +1,1065 @@ +//! Platform-agnostic interface for writing apps using [`egui`] (epi = egui programming interface). +//! +//! `epi` provides interfaces for window management and serialization. +//! +//! Start by looking at the [`App`] trait, and implement [`App::update`]. + +#![warn(missing_docs)] // Let's keep `epi` well-documented. + +#[cfg(target_arch = "wasm32")] +use std::any::Any; + +#[cfg(not(target_arch = "wasm32"))] +#[cfg(any(feature = "glow", feature = "wgpu"))] +pub use crate::native::run::UserEvent; + +#[cfg(not(target_arch = "wasm32"))] +#[cfg(any(feature = "glow", feature = "wgpu"))] +pub use winit::event_loop::EventLoopBuilder; + +/// Hook into the building of an event loop before it is run +/// +/// You can configure any platform specific details required on top of the default configuration +/// done by `EFrame`. +#[cfg(not(target_arch = "wasm32"))] +#[cfg(any(feature = "glow", feature = "wgpu"))] +pub type EventLoopBuilderHook = Box)>; + +/// This is how your app is created. +/// +/// You can use the [`CreationContext`] to setup egui, restore state, setup OpenGL things, etc. +pub type AppCreator = Box) -> Box>; + +/// Data that is passed to [`AppCreator`] that can be used to setup and initialize your app. +pub struct CreationContext<'s> { + /// The egui Context. + /// + /// You can use this to customize the look of egui, e.g to call [`egui::Context::set_fonts`], + /// [`egui::Context::set_visuals`] etc. + pub egui_ctx: egui::Context, + + /// Information about the surrounding environment. + pub integration_info: IntegrationInfo, + + /// You can use the storage to restore app state(requires the "persistence" feature). + pub storage: Option<&'s dyn Storage>, + + /// The [`glow::Context`] allows you to initialize OpenGL resources (e.g. shaders) that + /// you might want to use later from a [`egui::PaintCallback`]. + /// + /// Only available when compiling with the `glow` feature and using [`Renderer::Glow`]. + #[cfg(feature = "glow")] + pub gl: Option>, + + /// The underlying WGPU render state. + /// + /// Only available when compiling with the `wgpu` feature and using [`Renderer::Wgpu`]. + /// + /// Can be used to manage GPU resources for custom rendering with WGPU using [`egui::PaintCallback`]s. + #[cfg(feature = "wgpu")] + pub wgpu_render_state: Option, +} + +// ---------------------------------------------------------------------------- + +/// Implement this trait to write apps that can be compiled for both web/wasm and desktop/native using [`eframe`](https://github.com/emilk/egui/tree/master/crates/eframe). +pub trait App { + /// Called each time the UI needs repainting, which may be many times per second. + /// + /// Put your widgets into a [`egui::SidePanel`], [`egui::TopBottomPanel`], [`egui::CentralPanel`], [`egui::Window`] or [`egui::Area`]. + /// + /// The [`egui::Context`] can be cloned and saved if you like. + /// + /// To force a repaint, call [`egui::Context::request_repaint`] at any time (e.g. from another thread). + fn update(&mut self, ctx: &egui::Context, frame: &mut Frame); + + /// Get a handle to the app. + /// + /// Can be used from web to interact or other external context. + /// + /// You need to implement this if you want to be able to access the application from JS using [`crate::web::backend::AppRunner`]. + /// + /// This is needed because downcasting `Box` -> `Box` to get &`ConcreteApp` is not simple in current rust. + /// + /// Just copy-paste this as your implementation: + /// ```ignore + /// #[cfg(target_arch = "wasm32")] + /// fn as_any_mut(&mut self) -> Option<&mut dyn std::any::Any> { + /// Some(&mut *self) + /// } + /// ``` + #[cfg(target_arch = "wasm32")] + fn as_any_mut(&mut self) -> Option<&mut dyn Any> { + None + } + + /// Called on shutdown, and perhaps at regular intervals. Allows you to save state. + /// + /// Only called when the "persistence" feature is enabled. + /// + /// On web the state is stored to "Local Storage". + /// On native the path is picked using [`directories_next::ProjectDirs::data_dir`](https://docs.rs/directories-next/2.0.0/directories_next/struct.ProjectDirs.html#method.data_dir) which is: + /// * Linux: `/home/UserName/.local/share/APPNAME` + /// * macOS: `/Users/UserName/Library/Application Support/APPNAME` + /// * Windows: `C:\Users\UserName\AppData\Roaming\APPNAME` + /// + /// where `APPNAME` is what is given to `eframe::run_native`. + fn save(&mut self, _storage: &mut dyn Storage) {} + + /// Called when the user attempts to close the desktop window and/or quit the application. + /// + /// By returning `false` the closing will be aborted. To continue the closing return `true`. + /// + /// A scenario where this method will be run is after pressing the close button on a native + /// window, which allows you to ask the user whether they want to do something before exiting. + /// See the example at for practical usage. + /// + /// It will _not_ be called on the web or when the window is forcefully closed. + #[cfg(not(target_arch = "wasm32"))] + #[doc(alias = "exit")] + #[doc(alias = "quit")] + fn on_close_event(&mut self) -> bool { + true + } + + /// Called once on shutdown, after [`Self::save`]. + /// + /// If you need to abort an exit use [`Self::on_close_event`]. + /// + /// To get a [`glow`] context you need to compile with the `glow` feature flag, + /// and run eframe with the glow backend. + #[cfg(feature = "glow")] + fn on_exit(&mut self, _gl: Option<&glow::Context>) {} + + /// Called once on shutdown, after [`Self::save`]. + /// + /// If you need to abort an exit use [`Self::on_close_event`]. + #[cfg(not(feature = "glow"))] + fn on_exit(&mut self) {} + + // --------- + // Settings: + + /// Time between automatic calls to [`Self::save`] + fn auto_save_interval(&self) -> std::time::Duration { + std::time::Duration::from_secs(30) + } + + /// The size limit of the web app canvas. + /// + /// By default the max size is [`egui::Vec2::INFINITY`], i.e. unlimited. + fn max_size_points(&self) -> egui::Vec2 { + egui::Vec2::INFINITY + } + + /// Background color values for the app, e.g. what is sent to `gl.clearColor`. + /// + /// This is the background of your windows if you don't set a central panel. + /// + /// ATTENTION: + /// Since these float values go to the render as-is, any color space conversion as done + /// e.g. by converting from [`egui::Color32`] to [`egui::Rgba`] may cause incorrect results. + /// egui recommends that rendering backends use a normal "gamma-space" (non-sRGB-aware) blending, + /// which means the values you return here should also be in `sRGB` gamma-space in the 0-1 range. + /// You can use [`egui::Color32::to_normalized_gamma_f32`] for this. + fn clear_color(&self, _visuals: &egui::Visuals) -> [f32; 4] { + // NOTE: a bright gray makes the shadows of the windows look weird. + // We use a bit of transparency so that if the user switches on the + // `transparent()` option they get immediate results. + egui::Color32::from_rgba_unmultiplied(12, 12, 12, 180).to_normalized_gamma_f32() + + // _visuals.window_fill() would also be a natural choice + } + + /// Controls whether or not the native window position and size will be + /// persisted (only if the "persistence" feature is enabled). + fn persist_native_window(&self) -> bool { + true + } + + /// Controls whether or not the egui memory (window positions etc) will be + /// persisted (only if the "persistence" feature is enabled). + fn persist_egui_memory(&self) -> bool { + true + } + + /// If `true` a warm-up call to [`Self::update`] will be issued where + /// `ctx.memory(|mem| mem.everything_is_visible())` will be set to `true`. + /// + /// This can help pre-caching resources loaded by different parts of the UI, preventing stutter later on. + /// + /// In this warm-up call, all painted shapes will be ignored. + /// + /// The default is `false`, and it is unlikely you will want to change this. + fn warm_up_enabled(&self) -> bool { + false + } + + /// Called each time after the rendering the UI. + /// + /// Can be used to access pixel data with `get_pixels` + fn post_rendering(&mut self, _window_size_px: [u32; 2], _frame: &Frame) {} +} + +/// Selects the level of hardware graphics acceleration. +#[cfg(not(target_arch = "wasm32"))] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum HardwareAcceleration { + /// Require graphics acceleration. + Required, + + /// Prefer graphics acceleration, but fall back to software. + Preferred, + + /// Do NOT use graphics acceleration. + /// + /// On some platforms (MacOS) this is ignored and treated the same as [`Self::Preferred`]. + Off, +} + +/// Options controlling the behavior of a native window. +/// +/// Only a single native window is currently supported. +#[cfg(not(target_arch = "wasm32"))] +pub struct NativeOptions { + /// Sets whether or not the window will always be on top of other windows at initialization. + pub always_on_top: bool, + + /// Show window in maximized mode + pub maximized: bool, + + /// On desktop: add window decorations (i.e. a frame around your app)? + /// If false it will be difficult to move and resize the app. + pub decorated: bool, + + /// Start in (borderless) fullscreen? + /// + /// Default: `false`. + pub fullscreen: bool, + + /// On Mac: the window doesn't have a titlebar, but floating window buttons. + /// + /// See [winit's documentation][with_fullsize_content_view] for information on Mac-specific options. + /// + /// [with_fullsize_content_view]: https://docs.rs/winit/latest/x86_64-apple-darwin/winit/platform/macos/trait.WindowBuilderExtMacOS.html#tymethod.with_fullsize_content_view + #[cfg(target_os = "macos")] + pub fullsize_content: bool, + + /// On Windows: enable drag and drop support. Drag and drop can + /// not be disabled on other platforms. + /// + /// See [winit's documentation][drag_and_drop] for information on why you + /// might want to disable this on windows. + /// + /// [drag_and_drop]: https://docs.rs/winit/latest/x86_64-pc-windows-msvc/winit/platform/windows/trait.WindowBuilderExtWindows.html#tymethod.with_drag_and_drop + pub drag_and_drop_support: bool, + + /// The application icon, e.g. in the Windows task bar etc. + /// + /// This doesn't work on Mac and on Wayland. + /// See for more. + pub icon_data: Option, + + /// The initial (inner) position of the native window in points (logical pixels). + pub initial_window_pos: Option, + + /// The initial inner size of the native window in points (logical pixels). + pub initial_window_size: Option, + + /// The minimum inner window size in points (logical pixels). + pub min_window_size: Option, + + /// The maximum inner window size in points (logical pixels). + pub max_window_size: Option, + + /// Should the app window be resizable? + pub resizable: bool, + + /// On desktop: make the window transparent. + /// You control the transparency with [`App::clear_color()`]. + /// You should avoid having a [`egui::CentralPanel`], or make sure its frame is also transparent. + pub transparent: bool, + + /// On desktop: mouse clicks pass through the window, used for non-interactable overlays + /// Generally you would use this in conjunction with always_on_top + pub mouse_passthrough: bool, + + /// Turn on vertical syncing, limiting the FPS to the display refresh rate. + /// + /// The default is `true`. + pub vsync: bool, + + /// Set the level of the multisampling anti-aliasing (MSAA). + /// + /// Must be a power-of-two. Higher = more smooth 3D. + /// + /// A value of `0` turns it off (default). + /// + /// `egui` already performs anti-aliasing via "feathering" + /// (controlled by [`egui::epaint::TessellationOptions`]), + /// but if you are embedding 3D in egui you may want to turn on multisampling. + pub multisampling: u16, + + /// Sets the number of bits in the depth buffer. + /// + /// `egui` doesn't need the depth buffer, so the default value is 0. + /// + /// On `wgpu` backends, due to limited depth texture format options, this + /// will be interpreted as a boolean (non-zero = true) for whether or not + /// specifically a `Depth32Float` buffer is used. + pub depth_buffer: u8, + + /// Sets the number of bits in the stencil buffer. + /// + /// `egui` doesn't need the stencil buffer, so the default value is 0. + pub stencil_buffer: u8, + + /// Specify whether or not hardware acceleration is preferred, required, or not. + /// + /// Default: [`HardwareAcceleration::Preferred`]. + pub hardware_acceleration: HardwareAcceleration, + + /// What rendering backend to use. + #[cfg(any(feature = "glow", feature = "wgpu"))] + pub renderer: Renderer, + + /// Only used if the `dark-light` feature is enabled: + /// + /// Try to detect and follow the system preferred setting for dark vs light mode. + /// + /// By default, this is `true` on Mac and Windows, but `false` on Linux + /// due to . + /// + /// See also [`Self::default_theme`]. + pub follow_system_theme: bool, + + /// Which theme to use in case [`Self::follow_system_theme`] is `false` + /// or the `dark-light` feature is disabled. + /// + /// Default: [`Theme::Dark`]. + pub default_theme: Theme, + + /// This controls what happens when you close the main eframe window. + /// + /// If `true`, execution will continue after the eframe window is closed. + /// If `false`, the app will close once the eframe window is closed. + /// + /// This is `true` by default, and the `false` option is only there + /// so we can revert if we find any bugs. + /// + /// This feature was introduced in . + /// + /// When `true`, [`winit::platform::run_return::EventLoopExtRunReturn::run_return`] is used. + /// When `false`, [`winit::event_loop::EventLoop::run`] is used. + pub run_and_return: bool, + + /// Hook into the building of an event loop before it is run. + /// + /// Specify a callback here in case you need to make platform specific changes to the + /// event loop before it is run. + /// + /// Note: A [`NativeOptions`] clone will not include any `event_loop_builder` hook. + #[cfg(any(feature = "glow", feature = "wgpu"))] + pub event_loop_builder: Option, + + #[cfg(feature = "glow")] + /// Needed for cross compiling for VirtualBox VMSVGA driver with OpenGL ES 2.0 and OpenGL 2.1 which doesn't support SRGB texture. + /// See . + /// + /// For OpenGL ES 2.0: set this to [`egui_glow::ShaderVersion::Es100`] to solve blank texture problem (by using the "fallback shader"). + pub shader_version: Option, + + /// On desktop: make the window position to be centered at initialization. + /// + /// Platform specific: + /// + /// Wayland desktop currently not supported. + pub centered: bool, + + /// Configures wgpu instance/device/adapter/surface creation and renderloop. + #[cfg(feature = "wgpu")] + pub wgpu_options: egui_wgpu::WgpuConfiguration, +} + +#[cfg(not(target_arch = "wasm32"))] +impl Clone for NativeOptions { + fn clone(&self) -> Self { + Self { + icon_data: self.icon_data.clone(), + + #[cfg(any(feature = "glow", feature = "wgpu"))] + event_loop_builder: None, // Skip any builder callbacks if cloning + + #[cfg(feature = "wgpu")] + wgpu_options: self.wgpu_options.clone(), + + ..*self + } + } +} + +#[cfg(not(target_arch = "wasm32"))] +impl Default for NativeOptions { + fn default() -> Self { + Self { + always_on_top: false, + maximized: false, + decorated: true, + fullscreen: false, + + #[cfg(target_os = "macos")] + fullsize_content: false, + + drag_and_drop_support: true, + icon_data: None, + initial_window_pos: None, + initial_window_size: None, + min_window_size: None, + max_window_size: None, + resizable: true, + transparent: false, + mouse_passthrough: false, + vsync: true, + multisampling: 0, + depth_buffer: 0, + stencil_buffer: 0, + hardware_acceleration: HardwareAcceleration::Preferred, + + #[cfg(any(feature = "glow", feature = "wgpu"))] + renderer: Renderer::default(), + + follow_system_theme: cfg!(target_os = "macos") || cfg!(target_os = "windows"), + default_theme: Theme::Dark, + run_and_return: true, + + #[cfg(any(feature = "glow", feature = "wgpu"))] + event_loop_builder: None, + + #[cfg(feature = "glow")] + shader_version: None, + + centered: false, + + #[cfg(feature = "wgpu")] + wgpu_options: egui_wgpu::WgpuConfiguration::default(), + } + } +} + +#[cfg(not(target_arch = "wasm32"))] +impl NativeOptions { + /// The theme used by the system. + #[cfg(feature = "dark-light")] + pub fn system_theme(&self) -> Option { + if self.follow_system_theme { + crate::profile_scope!("dark_light::detect"); + match dark_light::detect() { + dark_light::Mode::Dark => Some(Theme::Dark), + dark_light::Mode::Light => Some(Theme::Light), + dark_light::Mode::Default => None, + } + } else { + None + } + } + + /// The theme used by the system. + #[cfg(not(feature = "dark-light"))] + pub fn system_theme(&self) -> Option { + None + } +} + +// ---------------------------------------------------------------------------- + +/// Options when using `eframe` in a web page. +#[cfg(target_arch = "wasm32")] +pub struct WebOptions { + /// Try to detect and follow the system preferred setting for dark vs light mode. + /// + /// See also [`Self::default_theme`]. + /// + /// Default: `true`. + pub follow_system_theme: bool, + + /// Which theme to use in case [`Self::follow_system_theme`] is `false` + /// or system theme detection fails. + /// + /// Default: `Theme::Dark`. + pub default_theme: Theme, + + /// Which version of WebGl context to select + /// + /// Default: [`WebGlContextOption::BestFirst`]. + #[cfg(feature = "glow")] + pub webgl_context_option: WebGlContextOption, + + /// Configures wgpu instance/device/adapter/surface creation and renderloop. + #[cfg(feature = "wgpu")] + pub wgpu_options: egui_wgpu::WgpuConfiguration, +} + +#[cfg(target_arch = "wasm32")] +impl Default for WebOptions { + fn default() -> Self { + Self { + follow_system_theme: true, + default_theme: Theme::Dark, + + #[cfg(feature = "glow")] + webgl_context_option: WebGlContextOption::BestFirst, + + #[cfg(feature = "wgpu")] + wgpu_options: egui_wgpu::WgpuConfiguration { + // WebGPU is not stable enough yet, use WebGL emulation + backends: wgpu::Backends::GL, + device_descriptor: wgpu::DeviceDescriptor { + label: Some("egui wgpu device"), + features: wgpu::Features::default(), + limits: wgpu::Limits { + // When using a depth buffer, we have to be able to create a texture + // large enough for the entire surface, and we want to support 4k+ displays. + max_texture_dimension_2d: 8192, + ..wgpu::Limits::downlevel_webgl2_defaults() + }, + }, + ..Default::default() + }, + } + } +} + +// ---------------------------------------------------------------------------- + +/// Dark or Light theme. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub enum Theme { + /// Dark mode: light text on a dark background. + Dark, + + /// Light mode: dark text on a light background. + Light, +} + +impl Theme { + /// Get the egui visuals corresponding to this theme. + /// + /// Use with [`egui::Context::set_visuals`]. + pub fn egui_visuals(self) -> egui::Visuals { + match self { + Self::Dark => egui::Visuals::dark(), + Self::Light => egui::Visuals::light(), + } + } +} + +// ---------------------------------------------------------------------------- + +/// WebGL Context options +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub enum WebGlContextOption { + /// Force Use WebGL1. + WebGl1, + + /// Force use WebGL2. + WebGl2, + + /// Use WebGl2 first. + BestFirst, + + /// Use WebGl1 first + CompatibilityFirst, +} + +// ---------------------------------------------------------------------------- + +/// What rendering backend to use. +/// +/// You need to enable the "glow" and "wgpu" features to have a choice. +#[cfg(any(feature = "glow", feature = "wgpu"))] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] +pub enum Renderer { + /// Use [`egui_glow`] renderer for [`glow`](https://github.com/grovesNL/glow). + #[cfg(feature = "glow")] + Glow, + + /// Use [`egui_wgpu`] renderer for [`wgpu`](https://github.com/gfx-rs/wgpu). + #[cfg(feature = "wgpu")] + Wgpu, +} + +#[cfg(any(feature = "glow", feature = "wgpu"))] +impl Default for Renderer { + fn default() -> Self { + #[cfg(feature = "glow")] + return Self::Glow; + + #[cfg(not(feature = "glow"))] + #[cfg(feature = "wgpu")] + return Self::Wgpu; + + #[cfg(not(feature = "glow"))] + #[cfg(not(feature = "wgpu"))] + compile_error!("eframe: you must enable at least one of the rendering backend features: 'glow' or 'wgpu'"); + } +} + +#[cfg(any(feature = "glow", feature = "wgpu"))] +impl std::fmt::Display for Renderer { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + #[cfg(feature = "glow")] + Self::Glow => "glow".fmt(f), + + #[cfg(feature = "wgpu")] + Self::Wgpu => "wgpu".fmt(f), + } + } +} + +#[cfg(any(feature = "glow", feature = "wgpu"))] +impl std::str::FromStr for Renderer { + type Err = String; + + fn from_str(name: &str) -> Result { + match name.to_lowercase().as_str() { + #[cfg(feature = "glow")] + "glow" => Ok(Self::Glow), + + #[cfg(feature = "wgpu")] + "wgpu" => Ok(Self::Wgpu), + + _ => Err(format!("eframe renderer {name:?} is not available. Make sure that the corresponding eframe feature is enabled.")) + } + } +} + +// ---------------------------------------------------------------------------- + +/// Image data for an application icon. +#[derive(Clone)] +pub struct IconData { + /// RGBA pixels, unmultiplied. + pub rgba: Vec, + + /// Image width. This should be a multiple of 4. + pub width: u32, + + /// Image height. This should be a multiple of 4. + pub height: u32, +} + +/// Represents the surroundings of your app. +/// +/// It provides methods to inspect the surroundings (are we on the web?), +/// allocate textures, and change settings (e.g. window size). +pub struct Frame { + /// Information about the integration. + pub(crate) info: IntegrationInfo, + + /// Where the app can issue commands back to the integration. + pub(crate) output: backend::AppOutput, + + /// A place where you can store custom data in a way that persists when you restart the app. + pub(crate) storage: Option>, + + /// A reference to the underlying [`glow`] (OpenGL) context. + #[cfg(feature = "glow")] + pub(crate) gl: Option>, + + /// Can be used to manage GPU resources for custom rendering with WGPU using [`egui::PaintCallback`]s. + #[cfg(feature = "wgpu")] + pub(crate) wgpu_render_state: Option, +} + +impl Frame { + /// True if you are in a web environment. + /// + /// Equivalent to `cfg!(target_arch = "wasm32")` + #[allow(clippy::unused_self)] + pub fn is_web(&self) -> bool { + cfg!(target_arch = "wasm32") + } + + /// Information about the integration. + pub fn info(&self) -> IntegrationInfo { + self.info.clone() + } + + /// A place where you can store custom data in a way that persists when you restart the app. + pub fn storage(&self) -> Option<&dyn Storage> { + self.storage.as_deref() + } + + /// A place where you can store custom data in a way that persists when you restart the app. + pub fn storage_mut(&mut self) -> Option<&mut (dyn Storage + 'static)> { + self.storage.as_deref_mut() + } + + /// A reference to the underlying [`glow`] (OpenGL) context. + /// + /// This can be used, for instance, to: + /// * Render things to offscreen buffers. + /// * Read the pixel buffer from the previous frame (`glow::Context::read_pixels`). + /// * Render things behind the egui windows. + /// + /// Note that all egui painting is deferred to after the call to [`App::update`] + /// ([`egui`] only collects [`egui::Shape`]s and then eframe paints them all in one go later on). + /// + /// To get a [`glow`] context you need to compile with the `glow` feature flag, + /// and run eframe using [`Renderer::Glow`]. + #[cfg(feature = "glow")] + pub fn gl(&self) -> Option<&std::sync::Arc> { + self.gl.as_ref() + } + + /// The underlying WGPU render state. + /// + /// Only available when compiling with the `wgpu` feature and using [`Renderer::Wgpu`]. + /// + /// Can be used to manage GPU resources for custom rendering with WGPU using [`egui::PaintCallback`]s. + #[cfg(feature = "wgpu")] + pub fn wgpu_render_state(&self) -> Option<&egui_wgpu::RenderState> { + self.wgpu_render_state.as_ref() + } + + /// Tell `eframe` to close the desktop window. + /// + /// The window will not close immediately, but at the end of the this frame. + /// + /// Calling this will likely result in the app quitting, unless + /// you have more code after the call to [`crate::run_native`]. + #[cfg(not(target_arch = "wasm32"))] + #[doc(alias = "exit")] + #[doc(alias = "quit")] + pub fn close(&mut self) { + tracing::debug!("eframe::Frame::close called"); + self.output.close = true; + } + + /// Minimize or unminimize window. (native only) + #[cfg(not(target_arch = "wasm32"))] + pub fn set_minimized(&mut self, minimized: bool) { + self.output.minimized = Some(minimized); + } + + /// Maximize or unmaximize window. (native only) + #[cfg(not(target_arch = "wasm32"))] + pub fn set_maximized(&mut self, maximized: bool) { + self.output.maximized = Some(maximized); + } + + /// Tell `eframe` to close the desktop window. + #[cfg(not(target_arch = "wasm32"))] + #[deprecated = "Renamed `close`"] + pub fn quit(&mut self) { + self.close(); + } + + /// Set the desired inner size of the window (in egui points). + #[cfg(not(target_arch = "wasm32"))] + pub fn set_window_size(&mut self, size: egui::Vec2) { + self.output.window_size = Some(size); + self.info.window_info.size = size; // so that subsequent calls see the updated value + } + + /// Set the desired title of the window. + #[cfg(not(target_arch = "wasm32"))] + pub fn set_window_title(&mut self, title: &str) { + self.output.window_title = Some(title.to_owned()); + } + + /// Set whether to show window decorations (i.e. a frame around you app). + /// + /// If false it will be difficult to move and resize the app. + #[cfg(not(target_arch = "wasm32"))] + pub fn set_decorations(&mut self, decorated: bool) { + self.output.decorated = Some(decorated); + } + + /// Turn borderless fullscreen on/off (native only). + #[cfg(not(target_arch = "wasm32"))] + pub fn set_fullscreen(&mut self, fullscreen: bool) { + self.output.fullscreen = Some(fullscreen); + self.info.window_info.fullscreen = fullscreen; // so that subsequent calls see the updated value + } + + /// set the position of the outer window. + #[cfg(not(target_arch = "wasm32"))] + pub fn set_window_pos(&mut self, pos: egui::Pos2) { + self.output.window_pos = Some(pos); + self.info.window_info.position = Some(pos); // so that subsequent calls see the updated value + } + + /// When called, the native window will follow the + /// movement of the cursor while the primary mouse button is down. + /// + /// Does not work on the web. + #[cfg(not(target_arch = "wasm32"))] + pub fn drag_window(&mut self) { + self.output.drag_window = true; + } + + /// Set the visibility of the window. + #[cfg(not(target_arch = "wasm32"))] + pub fn set_visible(&mut self, visible: bool) { + self.output.visible = Some(visible); + } + + /// On desktop: Set the window always on top. + /// + /// (Wayland desktop currently not supported) + #[cfg(not(target_arch = "wasm32"))] + pub fn set_always_on_top(&mut self, always_on_top: bool) { + self.output.always_on_top = Some(always_on_top); + } + + /// On desktop: Set the window to be centered. + /// + /// (Wayland desktop currently not supported) + #[cfg(not(target_arch = "wasm32"))] + pub fn set_centered(&mut self) { + if let Some(monitor_size) = self.info.window_info.monitor_size { + let inner_size = self.info.window_info.size; + if monitor_size.x > 1.0 && monitor_size.y > 1.0 { + let x = (monitor_size.x - inner_size.x) / 2.0; + let y = (monitor_size.y - inner_size.y) / 2.0; + self.set_window_pos(egui::Pos2 { x, y }); + } + } + } + + /// for integrations only: call once per frame + #[cfg(any(feature = "glow", feature = "wgpu"))] + pub(crate) fn take_app_output(&mut self) -> backend::AppOutput { + std::mem::take(&mut self.output) + } +} + +/// Information about the web environment (if applicable). +#[derive(Clone, Debug)] +#[cfg(target_arch = "wasm32")] +pub struct WebInfo { + /// The browser user agent. + pub user_agent: String, + + /// Information about the URL. + pub location: Location, +} + +/// Information about the application's main window, if available. +#[cfg(not(target_arch = "wasm32"))] +#[derive(Clone, Debug)] +pub struct WindowInfo { + /// Coordinates of the window's outer top left corner, relative to the top left corner of the first display. + /// + /// Unit: egui points (logical pixels). + /// + /// `None` = unknown. + pub position: Option, + + /// Are we in fullscreen mode? + pub fullscreen: bool, + + /// Are we minimized? + pub minimized: bool, + + /// Are we maximized? + pub maximized: bool, + + /// Window inner size in egui points (logical pixels). + pub size: egui::Vec2, + + /// Current monitor size in egui points (logical pixels) + pub monitor_size: Option, +} + +/// Information about the URL. +/// +/// Everything has been percent decoded (`%20` -> ` ` etc). +#[cfg(target_arch = "wasm32")] +#[derive(Clone, Debug)] +pub struct Location { + /// The full URL (`location.href`) without the hash. + /// + /// Example: `"http://www.example.com:80/index.html?foo=bar"`. + pub url: String, + + /// `location.protocol` + /// + /// Example: `"http:"`. + pub protocol: String, + + /// `location.host` + /// + /// Example: `"example.com:80"`. + pub host: String, + + /// `location.hostname` + /// + /// Example: `"example.com"`. + pub hostname: String, + + /// `location.port` + /// + /// Example: `"80"`. + pub port: String, + + /// The "#fragment" part of "www.example.com/index.html?query#fragment". + /// + /// Note that the leading `#` is included in the string. + /// Also known as "hash-link" or "anchor". + pub hash: String, + + /// The "query" part of "www.example.com/index.html?query#fragment". + /// + /// Note that the leading `?` is NOT included in the string. + /// + /// Use [`Self::query_map`] to get the parsed version of it. + pub query: String, + + /// The parsed "query" part of "www.example.com/index.html?query#fragment". + /// + /// "foo=42&bar%20" is parsed as `{"foo": "42", "bar ": ""}` + pub query_map: std::collections::BTreeMap, + + /// `location.origin` + /// + /// Example: `"http://www.example.com:80"`. + pub origin: String, +} + +/// Information about the integration passed to the use app each frame. +#[derive(Clone, Debug)] +pub struct IntegrationInfo { + /// Information about the surrounding web environment. + #[cfg(target_arch = "wasm32")] + pub web_info: WebInfo, + + /// Does the OS use dark or light mode? + /// + /// `None` means "don't know". + pub system_theme: Option, + + /// Seconds of cpu usage (in seconds) of UI code on the previous frame. + /// `None` if this is the first frame. + pub cpu_usage: Option, + + /// The OS native pixels-per-point + pub native_pixels_per_point: Option, + + /// The position and size of the native window. + #[cfg(not(target_arch = "wasm32"))] + pub window_info: WindowInfo, +} + +// ---------------------------------------------------------------------------- + +/// A place where you can store custom data in a way that persists when you restart the app. +/// +/// On the web this is backed by [local storage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage). +/// On desktop this is backed by the file system. +/// +/// See [`CreationContext::storage`] and [`App::save`]. +pub trait Storage { + /// Get the value for the given key. + fn get_string(&self, key: &str) -> Option; + + /// Set the value for the given key. + fn set_string(&mut self, key: &str, value: String); + + /// write-to-disk or similar + fn flush(&mut self); +} + +/// Stores nothing. +#[derive(Clone, Default)] +pub(crate) struct DummyStorage {} + +impl Storage for DummyStorage { + fn get_string(&self, _key: &str) -> Option { + None + } + + fn set_string(&mut self, _key: &str, _value: String) {} + + fn flush(&mut self) {} +} + +/// Get and deserialize the [RON](https://github.com/ron-rs/ron) stored at the given key. +#[cfg(feature = "ron")] +pub fn get_value(storage: &dyn Storage, key: &str) -> Option { + storage + .get_string(key) + .and_then(|value| ron::from_str(&value).ok()) +} + +/// Serialize the given value as [RON](https://github.com/ron-rs/ron) and store with the given key. +#[cfg(feature = "ron")] +pub fn set_value(storage: &mut dyn Storage, key: &str, value: &T) { + match ron::ser::to_string(value) { + Ok(string) => storage.set_string(key, string), + Err(err) => tracing::error!("eframe failed to encode data using ron: {}", err), + } +} + +/// [`Storage`] key used for app +pub const APP_KEY: &str = "app"; + +// ---------------------------------------------------------------------------- + +/// You only need to look here if you are writing a backend for `epi`. +pub(crate) mod backend { + /// Action that can be taken by the user app. + #[derive(Clone, Debug, Default)] + #[must_use] + pub struct AppOutput { + /// Set to `true` to close the native window (which often quits the app). + #[cfg(not(target_arch = "wasm32"))] + pub close: bool, + + /// Set to some size to resize the outer window (e.g. glium window) to this size. + #[cfg(not(target_arch = "wasm32"))] + pub window_size: Option, + + /// Set to some string to rename the outer window (e.g. glium window) to this title. + #[cfg(not(target_arch = "wasm32"))] + pub window_title: Option, + + /// Set to some bool to change window decorations. + #[cfg(not(target_arch = "wasm32"))] + pub decorated: Option, + + /// Set to some bool to change window fullscreen. + #[cfg(not(target_arch = "wasm32"))] // TODO(c2m): implement fullscreen on web + pub fullscreen: Option, + + /// Set to true to drag window while primary mouse button is down. + #[cfg(not(target_arch = "wasm32"))] + pub drag_window: bool, + + /// Set to some position to move the outer window (e.g. glium window) to this position + #[cfg(not(target_arch = "wasm32"))] + pub window_pos: Option, + + /// Set to some bool to change window visibility. + #[cfg(not(target_arch = "wasm32"))] + pub visible: Option, + + /// Set to some bool to tell the window always on top. + #[cfg(not(target_arch = "wasm32"))] + pub always_on_top: Option, + + /// Set to some bool to minimize or unminimize window. + #[cfg(not(target_arch = "wasm32"))] + pub minimized: Option, + + /// Set to some bool to maximize or unmaximize window. + #[cfg(not(target_arch = "wasm32"))] + pub maximized: Option, + } +} diff --git a/nevmes-gui/crates/eframe/src/lib.rs b/nevmes-gui/crates/eframe/src/lib.rs new file mode 100644 index 0000000..a2181c5 --- /dev/null +++ b/nevmes-gui/crates/eframe/src/lib.rs @@ -0,0 +1,262 @@ +//! eframe - the [`egui`] framework crate +//! +//! If you are planning to write an app for web or native, +//! and want to use [`egui`] for everything, then `eframe` is for you! +//! +//! To get started, see the [examples](https://github.com/emilk/egui/tree/master/examples). +//! To learn how to set up `eframe` for web and native, go to and follow the instructions there! +//! +//! In short, you implement [`App`] (especially [`App::update`]) and then +//! call [`crate::run_native`] from your `main.rs`, and/or call `eframe::start_web` from your `lib.rs`. +//! +//! ## Usage, native: +//! ``` no_run +//! use eframe::egui; +//! +//! fn main() { +//! let native_options = eframe::NativeOptions::default(); +//! eframe::run_native("My egui App", native_options, Box::new(|cc| Box::new(MyEguiApp::new(cc)))); +//! } +//! +//! #[derive(Default)] +//! struct MyEguiApp {} +//! +//! impl MyEguiApp { +//! fn new(cc: &eframe::CreationContext<'_>) -> Self { +//! // Customize egui here with cc.egui_ctx.set_fonts and cc.egui_ctx.set_visuals. +//! // Restore app state using cc.storage (requires the "persistence" feature). +//! // Use the cc.gl (a glow::Context) to create graphics shaders and buffers that you can use +//! // for e.g. egui::PaintCallback. +//! Self::default() +//! } +//! } +//! +//! impl eframe::App for MyEguiApp { +//! fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { +//! egui::CentralPanel::default().show(ctx, |ui| { +//! ui.heading("Hello World!"); +//! }); +//! } +//! } +//! ``` +//! +//! ## Usage, web: +//! ``` no_run +//! #[cfg(target_arch = "wasm32")] +//! use wasm_bindgen::prelude::*; +//! +//! /// Call this once from the HTML. +//! #[cfg(target_arch = "wasm32")] +//! #[wasm_bindgen] +//! pub async fn start(canvas_id: &str) -> Result { +//! let web_options = eframe::WebOptions::default(); +//! eframe::start_web(canvas_id, web_options, Box::new(|cc| Box::new(MyEguiApp::new(cc)))).await +//! } +//! ``` +//! +//! ## Feature flags +#![cfg_attr(feature = "document-features", doc = document_features::document_features!())] +//! + +#![allow(clippy::needless_doctest_main)] + +// Re-export all useful libraries: +pub use {egui, egui::emath, egui::epaint}; + +#[cfg(feature = "glow")] +pub use {egui_glow, glow}; + +#[cfg(feature = "wgpu")] +pub use {egui_wgpu, wgpu}; + +mod epi; + +// Re-export everything in `epi` so `eframe` users don't have to care about what `epi` is: +pub use epi::*; + +// ---------------------------------------------------------------------------- +// When compiling for web + +#[cfg(target_arch = "wasm32")] +pub mod web; + +#[cfg(target_arch = "wasm32")] +pub use wasm_bindgen; + +#[cfg(target_arch = "wasm32")] +use web::AppRunnerRef; + +#[cfg(target_arch = "wasm32")] +pub use web_sys; + +/// Install event listeners to register different input events +/// and start running the given app. +/// +/// ``` no_run +/// #[cfg(target_arch = "wasm32")] +/// use wasm_bindgen::prelude::*; +/// +/// /// This is the entry-point for all the web-assembly. +/// /// This is called from the HTML. +/// /// It loads the app, installs some callbacks, then returns. +/// /// It returns a handle to the running app that can be stopped calling `AppRunner::stop_web`. +/// /// You can add more callbacks like this if you want to call in to your code. +/// #[cfg(target_arch = "wasm32")] +/// #[wasm_bindgen] +/// pub struct WebHandle { +/// handle: AppRunnerRef, +/// } +/// #[cfg(target_arch = "wasm32")] +/// #[wasm_bindgen] +/// pub async fn start(canvas_id: &str) -> Result { +/// let web_options = eframe::WebOptions::default(); +/// eframe::start_web( +/// canvas_id, +/// web_options, +/// Box::new(|cc| Box::new(MyEguiApp::new(cc))), +/// ) +/// .await +/// .map(|handle| WebHandle { handle }) +/// } +/// ``` +/// +/// # Errors +/// Failing to initialize WebGL graphics. +#[cfg(target_arch = "wasm32")] +pub async fn start_web( + canvas_id: &str, + web_options: WebOptions, + app_creator: AppCreator, +) -> std::result::Result { + let handle = web::start(canvas_id, web_options, app_creator).await?; + + Ok(handle) +} + +// ---------------------------------------------------------------------------- +// When compiling natively + +#[cfg(not(target_arch = "wasm32"))] +#[cfg(any(feature = "glow", feature = "wgpu"))] +mod native; + +/// This is how you start a native (desktop) app. +/// +/// The first argument is name of your app, used for the title bar of the native window +/// and the save location of persistence (see [`App::save`]). +/// +/// Call from `fn main` like this: +/// ``` no_run +/// use eframe::egui; +/// +/// fn main() { +/// let native_options = eframe::NativeOptions::default(); +/// eframe::run_native("MyApp", native_options, Box::new(|cc| Box::new(MyEguiApp::new(cc)))); +/// } +/// +/// #[derive(Default)] +/// struct MyEguiApp {} +/// +/// impl MyEguiApp { +/// fn new(cc: &eframe::CreationContext<'_>) -> Self { +/// // Customize egui here with cc.egui_ctx.set_fonts and cc.egui_ctx.set_visuals. +/// // Restore app state using cc.storage (requires the "persistence" feature). +/// // Use the cc.gl (a glow::Context) to create graphics shaders and buffers that you can use +/// // for e.g. egui::PaintCallback. +/// Self::default() +/// } +/// } +/// +/// impl eframe::App for MyEguiApp { +/// fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { +/// egui::CentralPanel::default().show(ctx, |ui| { +/// ui.heading("Hello World!"); +/// }); +/// } +/// } +/// ``` +/// +/// # Errors +/// This function can fail if we fail to set up a graphics context. +#[cfg(not(target_arch = "wasm32"))] +#[allow(clippy::needless_pass_by_value)] +#[cfg(any(feature = "glow", feature = "wgpu"))] +pub fn run_native( + app_name: &str, + native_options: NativeOptions, + app_creator: AppCreator, +) -> Result<()> { + let renderer = native_options.renderer; + + #[cfg(not(feature = "__screenshot"))] + assert!( + std::env::var("EFRAME_SCREENSHOT_TO").is_err(), + "EFRAME_SCREENSHOT_TO found without compiling with the '__screenshot' feature" + ); + + match renderer { + #[cfg(feature = "glow")] + Renderer::Glow => { + tracing::debug!("Using the glow renderer"); + native::run::run_glow(app_name, native_options, app_creator) + } + + #[cfg(feature = "wgpu")] + Renderer::Wgpu => { + tracing::debug!("Using the wgpu renderer"); + native::run::run_wgpu(app_name, native_options, app_creator) + } + } +} + +// ---------------------------------------------------------------------------- + +/// The different problems that can occur when trying to run `eframe`. +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[cfg(not(target_arch = "wasm32"))] + #[error("winit error: {0}")] + Winit(#[from] winit::error::OsError), + + #[cfg(all(feature = "glow", not(target_arch = "wasm32")))] + #[error("glutin error: {0}")] + Glutin(#[from] glutin::error::Error), + + #[cfg(all(feature = "glow", not(target_arch = "wasm32")))] + #[error("Found no glutin configs matching the template: {0:?}. error: {1:?}")] + NoGlutinConfigs(glutin::config::ConfigTemplate, Box), + + #[cfg(feature = "wgpu")] + #[error("WGPU error: {0}")] + Wgpu(#[from] egui_wgpu::WgpuError), +} + +pub type Result = std::result::Result; + +// --------------------------------------------------------------------------- + +#[cfg(not(target_arch = "wasm32"))] +#[cfg(any(feature = "glow", feature = "wgpu"))] +mod profiling_scopes { + /// Profiling macro for feature "puffin" + macro_rules! profile_function { + ($($arg: tt)*) => { + #[cfg(feature = "puffin")] + puffin::profile_function!($($arg)*); + }; +} + pub(crate) use profile_function; + + /// Profiling macro for feature "puffin" + macro_rules! profile_scope { + ($($arg: tt)*) => { + #[cfg(feature = "puffin")] + puffin::profile_scope!($($arg)*); + }; +} + pub(crate) use profile_scope; +} + +#[cfg(not(target_arch = "wasm32"))] +#[cfg(any(feature = "glow", feature = "wgpu"))] +pub(crate) use profiling_scopes::*; diff --git a/nevmes-gui/crates/eframe/src/native/epi_integration.rs b/nevmes-gui/crates/eframe/src/native/epi_integration.rs new file mode 100644 index 0000000..6e09ca7 --- /dev/null +++ b/nevmes-gui/crates/eframe/src/native/epi_integration.rs @@ -0,0 +1,568 @@ +use winit::event_loop::EventLoopWindowTarget; + +#[cfg(target_os = "macos")] +use winit::platform::macos::WindowBuilderExtMacOS as _; + +#[cfg(feature = "accesskit")] +use egui::accesskit; +use egui::NumExt as _; +#[cfg(feature = "accesskit")] +use egui_winit::accesskit_winit; +use egui_winit::{native_pixels_per_point, EventResponse, WindowSettings}; + +use crate::{epi, Theme, WindowInfo}; + +#[derive(Default)] +pub struct WindowState { + // We cannot simply call `winit::Window::is_minimized/is_maximized` + // because that deadlocks on mac. + pub minimized: bool, + pub maximized: bool, +} + +pub fn points_to_size(points: egui::Vec2) -> winit::dpi::LogicalSize { + winit::dpi::LogicalSize { + width: points.x as f64, + height: points.y as f64, + } +} + +pub fn read_window_info( + window: &winit::window::Window, + pixels_per_point: f32, + window_state: &WindowState, +) -> WindowInfo { + let position = window + .outer_position() + .ok() + .map(|pos| pos.to_logical::(pixels_per_point.into())) + .map(|pos| egui::Pos2 { x: pos.x, y: pos.y }); + + let monitor = window.current_monitor().is_some(); + let monitor_size = if monitor { + let size = window + .current_monitor() + .unwrap() + .size() + .to_logical::(pixels_per_point.into()); + Some(egui::vec2(size.width, size.height)) + } else { + None + }; + + let size = window + .inner_size() + .to_logical::(pixels_per_point.into()); + + // NOTE: calling window.is_minimized() or window.is_maximized() deadlocks on Mac. + + WindowInfo { + position, + fullscreen: window.fullscreen().is_some(), + minimized: window_state.minimized, + maximized: window_state.maximized, + size: egui::Vec2 { + x: size.width, + y: size.height, + }, + monitor_size, + } +} + +pub fn window_builder( + event_loop: &EventLoopWindowTarget, + title: &str, + native_options: &epi::NativeOptions, + window_settings: Option, +) -> winit::window::WindowBuilder { + let epi::NativeOptions { + maximized, + decorated, + fullscreen, + #[cfg(target_os = "macos")] + fullsize_content, + drag_and_drop_support, + icon_data, + initial_window_pos, + initial_window_size, + min_window_size, + max_window_size, + resizable, + transparent, + centered, + .. + } = native_options; + + let window_icon = icon_data.clone().and_then(load_icon); + + let mut window_builder = winit::window::WindowBuilder::new() + .with_title(title) + .with_decorations(*decorated) + .with_fullscreen(fullscreen.then(|| winit::window::Fullscreen::Borderless(None))) + .with_maximized(*maximized) + .with_resizable(*resizable) + .with_transparent(*transparent) + .with_window_icon(window_icon) + // Keep hidden until we've painted something. See https://github.com/emilk/egui/pull/2279 + // We must also keep the window hidden until AccessKit is initialized. + .with_visible(false); + + #[cfg(target_os = "macos")] + if *fullsize_content { + window_builder = window_builder + .with_title_hidden(true) + .with_titlebar_transparent(true) + .with_fullsize_content_view(true); + } + + if let Some(min_size) = *min_window_size { + window_builder = window_builder.with_min_inner_size(points_to_size(min_size)); + } + if let Some(max_size) = *max_window_size { + window_builder = window_builder.with_max_inner_size(points_to_size(max_size)); + } + + window_builder = window_builder_drag_and_drop(window_builder, *drag_and_drop_support); + + let inner_size_points = if let Some(mut window_settings) = window_settings { + // Restore pos/size from previous session + window_settings.clamp_to_sane_values(largest_monitor_point_size(event_loop)); + #[cfg(windows)] + window_settings.clamp_window_to_sane_position(&event_loop); + window_builder = window_settings.initialize_window(window_builder); + window_settings.inner_size_points() + } else { + if let Some(pos) = *initial_window_pos { + window_builder = window_builder.with_position(winit::dpi::LogicalPosition { + x: pos.x as f64, + y: pos.y as f64, + }); + } + + if let Some(initial_window_size) = *initial_window_size { + let initial_window_size = + initial_window_size.at_most(largest_monitor_point_size(event_loop)); + window_builder = window_builder.with_inner_size(points_to_size(initial_window_size)); + } + + *initial_window_size + }; + + if *centered { + if let Some(monitor) = event_loop.available_monitors().next() { + let monitor_size = monitor.size().to_logical::(monitor.scale_factor()); + let inner_size = inner_size_points.unwrap_or(egui::Vec2 { x: 800.0, y: 600.0 }); + if monitor_size.width > 0.0 && monitor_size.height > 0.0 { + let x = (monitor_size.width - inner_size.x as f64) / 2.0; + let y = (monitor_size.height - inner_size.y as f64) / 2.0; + window_builder = window_builder.with_position(winit::dpi::LogicalPosition { x, y }); + } + } + } + window_builder +} + +pub fn apply_native_options_to_window( + window: &winit::window::Window, + native_options: &crate::NativeOptions, +) { + use winit::window::WindowLevel; + window.set_window_level(if native_options.always_on_top { + WindowLevel::AlwaysOnTop + } else { + WindowLevel::Normal + }); +} + +fn largest_monitor_point_size(event_loop: &EventLoopWindowTarget) -> egui::Vec2 { + let mut max_size = egui::Vec2::ZERO; + + for monitor in event_loop.available_monitors() { + let size = monitor.size().to_logical::(monitor.scale_factor()); + let size = egui::vec2(size.width, size.height); + max_size = max_size.max(size); + } + + if max_size == egui::Vec2::ZERO { + egui::Vec2::splat(16000.0) + } else { + max_size + } +} + +fn load_icon(icon_data: epi::IconData) -> Option { + winit::window::Icon::from_rgba(icon_data.rgba, icon_data.width, icon_data.height).ok() +} + +#[cfg(target_os = "windows")] +fn window_builder_drag_and_drop( + window_builder: winit::window::WindowBuilder, + enable: bool, +) -> winit::window::WindowBuilder { + use winit::platform::windows::WindowBuilderExtWindows as _; + window_builder.with_drag_and_drop(enable) +} + +#[cfg(not(target_os = "windows"))] +fn window_builder_drag_and_drop( + window_builder: winit::window::WindowBuilder, + _enable: bool, +) -> winit::window::WindowBuilder { + // drag and drop can only be disabled on windows + window_builder +} + +pub fn handle_app_output( + window: &winit::window::Window, + current_pixels_per_point: f32, + app_output: epi::backend::AppOutput, + window_state: &mut WindowState, +) { + let epi::backend::AppOutput { + close: _, + window_size, + window_title, + decorated, + fullscreen, + drag_window, + window_pos, + visible: _, // handled in post_present + always_on_top, + minimized, + maximized, + } = app_output; + + if let Some(decorated) = decorated { + window.set_decorations(decorated); + } + + if let Some(window_size) = window_size { + window.set_inner_size( + winit::dpi::PhysicalSize { + width: (current_pixels_per_point * window_size.x).round(), + height: (current_pixels_per_point * window_size.y).round(), + } + .to_logical::(native_pixels_per_point(window) as f64), + ); + } + + if let Some(fullscreen) = fullscreen { + window.set_fullscreen(fullscreen.then_some(winit::window::Fullscreen::Borderless(None))); + } + + if let Some(window_title) = window_title { + window.set_title(&window_title); + } + + if let Some(window_pos) = window_pos { + window.set_outer_position(winit::dpi::LogicalPosition { + x: window_pos.x as f64, + y: window_pos.y as f64, + }); + } + + if drag_window { + let _ = window.drag_window(); + } + + if let Some(always_on_top) = always_on_top { + use winit::window::WindowLevel; + window.set_window_level(if always_on_top { + WindowLevel::AlwaysOnTop + } else { + WindowLevel::Normal + }); + } + + if let Some(minimized) = minimized { + window.set_minimized(minimized); + window_state.minimized = minimized; + } + + if let Some(maximized) = maximized { + window.set_maximized(maximized); + window_state.maximized = maximized; + } +} + +// ---------------------------------------------------------------------------- + +/// For loading/saving app state and/or egui memory to disk. +pub fn create_storage(_app_name: &str) -> Option> { + #[cfg(feature = "persistence")] + if let Some(storage) = super::file_storage::FileStorage::from_app_name(_app_name) { + return Some(Box::new(storage)); + } + None +} + +// ---------------------------------------------------------------------------- + +/// Everything needed to make a winit-based integration for [`epi`]. +pub struct EpiIntegration { + pub frame: epi::Frame, + last_auto_save: std::time::Instant, + pub egui_ctx: egui::Context, + pending_full_output: egui::FullOutput, + egui_winit: egui_winit::State, + /// When set, it is time to close the native window. + close: bool, + can_drag_window: bool, + window_state: WindowState, +} + +impl EpiIntegration { + pub fn new( + event_loop: &EventLoopWindowTarget, + max_texture_side: usize, + window: &winit::window::Window, + system_theme: Option, + storage: Option>, + #[cfg(feature = "glow")] gl: Option>, + #[cfg(feature = "wgpu")] wgpu_render_state: Option, + ) -> Self { + let egui_ctx = egui::Context::default(); + + let memory = load_egui_memory(storage.as_deref()).unwrap_or_default(); + egui_ctx.memory_mut(|mem| *mem = memory); + + let native_pixels_per_point = window.scale_factor() as f32; + + let window_state = WindowState { + minimized: window.is_minimized().unwrap_or(false), + maximized: window.is_maximized(), + }; + + let frame = epi::Frame { + info: epi::IntegrationInfo { + system_theme, + cpu_usage: None, + native_pixels_per_point: Some(native_pixels_per_point), + window_info: read_window_info(window, egui_ctx.pixels_per_point(), &window_state), + }, + output: epi::backend::AppOutput { + visible: Some(true), + ..Default::default() + }, + storage, + #[cfg(feature = "glow")] + gl, + #[cfg(feature = "wgpu")] + wgpu_render_state, + }; + + let mut egui_winit = egui_winit::State::new(event_loop); + egui_winit.set_max_texture_side(max_texture_side); + egui_winit.set_pixels_per_point(native_pixels_per_point); + + Self { + frame, + last_auto_save: std::time::Instant::now(), + egui_ctx, + egui_winit, + pending_full_output: Default::default(), + close: false, + can_drag_window: false, + window_state, + } + } + + #[cfg(feature = "accesskit")] + pub fn init_accesskit + Send>( + &mut self, + window: &winit::window::Window, + event_loop_proxy: winit::event_loop::EventLoopProxy, + ) { + let egui_ctx = self.egui_ctx.clone(); + self.egui_winit + .init_accesskit(window, event_loop_proxy, move || { + // This function is called when an accessibility client + // (e.g. screen reader) makes its first request. If we got here, + // we know that an accessibility tree is actually wanted. + egui_ctx.enable_accesskit(); + // Enqueue a repaint so we'll receive a full tree update soon. + egui_ctx.request_repaint(); + egui_ctx.accesskit_placeholder_tree_update() + }); + } + + pub fn warm_up(&mut self, app: &mut dyn epi::App, window: &winit::window::Window) { + crate::profile_function!(); + let saved_memory: egui::Memory = self.egui_ctx.memory(|mem| mem.clone()); + self.egui_ctx + .memory_mut(|mem| mem.set_everything_is_visible(true)); + let full_output = self.update(app, window); + self.pending_full_output.append(full_output); // Handle it next frame + self.egui_ctx.memory_mut(|mem| *mem = saved_memory); // We don't want to remember that windows were huge. + self.egui_ctx.clear_animations(); + } + + /// If `true`, it is time to close the native window. + pub fn should_close(&self) -> bool { + self.close + } + + pub fn on_event( + &mut self, + app: &mut dyn epi::App, + event: &winit::event::WindowEvent<'_>, + ) -> EventResponse { + use winit::event::{ElementState, MouseButton, WindowEvent}; + + match event { + WindowEvent::CloseRequested => { + tracing::debug!("Received WindowEvent::CloseRequested"); + self.close = app.on_close_event(); + tracing::debug!("App::on_close_event returned {}", self.close); + } + WindowEvent::Destroyed => { + tracing::debug!("Received WindowEvent::Destroyed"); + self.close = true; + } + WindowEvent::MouseInput { + button: MouseButton::Left, + state: ElementState::Pressed, + .. + } => self.can_drag_window = true, + WindowEvent::ScaleFactorChanged { scale_factor, .. } => { + self.frame.info.native_pixels_per_point = Some(*scale_factor as _); + } + _ => {} + } + + self.egui_winit.on_event(&self.egui_ctx, event) + } + + #[cfg(feature = "accesskit")] + pub fn on_accesskit_action_request(&mut self, request: accesskit::ActionRequest) { + self.egui_winit.on_accesskit_action_request(request); + } + + pub fn update( + &mut self, + app: &mut dyn epi::App, + window: &winit::window::Window, + ) -> egui::FullOutput { + let frame_start = std::time::Instant::now(); + + self.frame.info.window_info = + read_window_info(window, self.egui_ctx.pixels_per_point(), &self.window_state); + let raw_input = self.egui_winit.take_egui_input(window); + + // Run user code: + let full_output = self.egui_ctx.run(raw_input, |egui_ctx| { + crate::profile_scope!("App::update"); + app.update(egui_ctx, &mut self.frame); + }); + + self.pending_full_output.append(full_output); + let full_output = std::mem::take(&mut self.pending_full_output); + + { + let mut app_output = self.frame.take_app_output(); + app_output.drag_window &= self.can_drag_window; // Necessary on Windows; see https://github.com/emilk/egui/pull/1108 + self.can_drag_window = false; + if app_output.close { + self.close = app.on_close_event(); + tracing::debug!("App::on_close_event returned {}", self.close); + } + self.frame.output.visible = app_output.visible; // this is handled by post_present + handle_app_output( + window, + self.egui_ctx.pixels_per_point(), + app_output, + &mut self.window_state, + ); + } + + let frame_time = frame_start.elapsed().as_secs_f64() as f32; + self.frame.info.cpu_usage = Some(frame_time); + + full_output + } + + pub fn post_rendering(&mut self, app: &mut dyn epi::App, window: &winit::window::Window) { + let inner_size = window.inner_size(); + let window_size_px = [inner_size.width, inner_size.height]; + + app.post_rendering(window_size_px, &self.frame); + } + + pub fn post_present(&mut self, window: &winit::window::Window) { + if let Some(visible) = self.frame.output.visible.take() { + window.set_visible(visible); + } + } + + pub fn handle_platform_output( + &mut self, + window: &winit::window::Window, + platform_output: egui::PlatformOutput, + ) { + self.egui_winit + .handle_platform_output(window, &self.egui_ctx, platform_output); + } + + // ------------------------------------------------------------------------ + // Persistance stuff: + + pub fn maybe_autosave(&mut self, app: &mut dyn epi::App, window: &winit::window::Window) { + let now = std::time::Instant::now(); + if now - self.last_auto_save > app.auto_save_interval() { + self.save(app, window); + self.last_auto_save = now; + } + } + + pub fn save(&mut self, _app: &mut dyn epi::App, _window: &winit::window::Window) { + #[cfg(feature = "persistence")] + if let Some(storage) = self.frame.storage_mut() { + crate::profile_function!(); + + if _app.persist_native_window() { + crate::profile_scope!("native_window"); + epi::set_value( + storage, + STORAGE_WINDOW_KEY, + &WindowSettings::from_display(_window), + ); + } + if _app.persist_egui_memory() { + crate::profile_scope!("egui_memory"); + self.egui_ctx + .memory(|mem| epi::set_value(storage, STORAGE_EGUI_MEMORY_KEY, mem)); + } + { + crate::profile_scope!("App::save"); + _app.save(storage); + } + + crate::profile_scope!("Storage::flush"); + storage.flush(); + } + } +} + +#[cfg(feature = "persistence")] +const STORAGE_EGUI_MEMORY_KEY: &str = "egui"; + +#[cfg(feature = "persistence")] +const STORAGE_WINDOW_KEY: &str = "window"; + +pub fn load_window_settings(_storage: Option<&dyn epi::Storage>) -> Option { + #[cfg(feature = "persistence")] + { + epi::get_value(_storage?, STORAGE_WINDOW_KEY) + } + #[cfg(not(feature = "persistence"))] + None +} + +pub fn load_egui_memory(_storage: Option<&dyn epi::Storage>) -> Option { + #[cfg(feature = "persistence")] + { + epi::get_value(_storage?, STORAGE_EGUI_MEMORY_KEY) + } + #[cfg(not(feature = "persistence"))] + None +} diff --git a/nevmes-gui/crates/eframe/src/native/file_storage.rs b/nevmes-gui/crates/eframe/src/native/file_storage.rs new file mode 100644 index 0000000..331f40f --- /dev/null +++ b/nevmes-gui/crates/eframe/src/native/file_storage.rs @@ -0,0 +1,117 @@ +use std::{ + collections::HashMap, + path::{Path, PathBuf}, +}; + +// ---------------------------------------------------------------------------- + +/// A key-value store backed by a [RON](https://github.com/ron-rs/ron) file on disk. +/// Used to restore egui state, glium window position/size and app state. +pub struct FileStorage { + ron_filepath: PathBuf, + kv: HashMap, + dirty: bool, + last_save_join_handle: Option>, +} + +impl Drop for FileStorage { + fn drop(&mut self) { + if let Some(join_handle) = self.last_save_join_handle.take() { + join_handle.join().ok(); + } + } +} + +impl FileStorage { + /// Store the state in this .ron file. + pub fn from_ron_filepath(ron_filepath: impl Into) -> Self { + let ron_filepath: PathBuf = ron_filepath.into(); + tracing::debug!("Loading app state from {:?}…", ron_filepath); + Self { + kv: read_ron(&ron_filepath).unwrap_or_default(), + ron_filepath, + dirty: false, + last_save_join_handle: None, + } + } + + /// Find a good place to put the files that the OS likes. + pub fn from_app_name(app_name: &str) -> Option { + if let Some(proj_dirs) = directories_next::ProjectDirs::from("", "", app_name) { + let data_dir = proj_dirs.data_dir().to_path_buf(); + if let Err(err) = std::fs::create_dir_all(&data_dir) { + tracing::warn!( + "Saving disabled: Failed to create app path at {:?}: {}", + data_dir, + err + ); + None + } else { + Some(Self::from_ron_filepath(data_dir.join("app.ron"))) + } + } else { + tracing::warn!("Saving disabled: Failed to find path to data_dir."); + None + } + } +} + +impl crate::Storage for FileStorage { + fn get_string(&self, key: &str) -> Option { + self.kv.get(key).cloned() + } + + fn set_string(&mut self, key: &str, value: String) { + if self.kv.get(key) != Some(&value) { + self.kv.insert(key.to_owned(), value); + self.dirty = true; + } + } + + fn flush(&mut self) { + if self.dirty { + self.dirty = false; + + let file_path = self.ron_filepath.clone(); + let kv = self.kv.clone(); + + if let Some(join_handle) = self.last_save_join_handle.take() { + // wait for previous save to complete. + join_handle.join().ok(); + } + + let join_handle = std::thread::spawn(move || { + let file = std::fs::File::create(&file_path).unwrap(); + let config = Default::default(); + ron::ser::to_writer_pretty(file, &kv, config).unwrap(); + tracing::trace!("Persisted to {:?}", file_path); + }); + + self.last_save_join_handle = Some(join_handle); + } + } +} + +// ---------------------------------------------------------------------------- + +fn read_ron(ron_path: impl AsRef) -> Option +where + T: serde::de::DeserializeOwned, +{ + match std::fs::File::open(ron_path) { + Ok(file) => { + let reader = std::io::BufReader::new(file); + match ron::de::from_reader(reader) { + Ok(value) => Some(value), + Err(err) => { + tracing::warn!("Failed to parse RON: {}", err); + None + } + } + } + Err(_err) => { + // File probably doesn't exist. That's fine. + None + } + } +} diff --git a/nevmes-gui/crates/eframe/src/native/mod.rs b/nevmes-gui/crates/eframe/src/native/mod.rs new file mode 100644 index 0000000..3718d60 --- /dev/null +++ b/nevmes-gui/crates/eframe/src/native/mod.rs @@ -0,0 +1,6 @@ +mod epi_integration; +pub mod run; + +/// File storage which can be used by native backends. +#[cfg(feature = "persistence")] +pub mod file_storage; diff --git a/nevmes-gui/crates/eframe/src/native/run.rs b/nevmes-gui/crates/eframe/src/native/run.rs new file mode 100644 index 0000000..fa687c5 --- /dev/null +++ b/nevmes-gui/crates/eframe/src/native/run.rs @@ -0,0 +1,1423 @@ +//! Note that this file contains two similar paths - one for [`glow`], one for [`wgpu`]. +//! When making changes to one you often also want to apply it to the other. + +use std::time::{Duration, Instant}; + +use winit::event_loop::{ + ControlFlow, EventLoop, EventLoopBuilder, EventLoopProxy, EventLoopWindowTarget, +}; + +#[cfg(feature = "accesskit")] +use egui_winit::accesskit_winit; +use egui_winit::winit; + +use crate::{epi, Result}; + +use super::epi_integration::{self, EpiIntegration}; + +// ---------------------------------------------------------------------------- + +#[derive(Debug)] +pub enum UserEvent { + RequestRepaint, + #[cfg(feature = "accesskit")] + AccessKitActionRequest(accesskit_winit::ActionRequestEvent), +} + +#[cfg(feature = "accesskit")] +impl From for UserEvent { + fn from(inner: accesskit_winit::ActionRequestEvent) -> Self { + Self::AccessKitActionRequest(inner) + } +} + +// ---------------------------------------------------------------------------- + +pub use epi::NativeOptions; + +#[derive(Debug)] +enum EventResult { + Wait, + + /// Causes a synchronous repaint inside the event handler. This should only + /// be used in special situations if the window must be repainted while + /// handling a specific event. This occurs on Windows when handling resizes. + /// + /// `RepaintNow` creates a new frame synchronously, and should therefore + /// only be used for extremely urgent repaints. + RepaintNow, + + /// Queues a repaint for once the event loop handles its next redraw. Exists + /// so that multiple input events can be handled in one frame. Does not + /// cause any delay like `RepaintNow`. + RepaintNext, + + RepaintAt(Instant), + + Exit, +} + +trait WinitApp { + fn is_focused(&self) -> bool; + + fn integration(&self) -> Option<&EpiIntegration>; + + fn window(&self) -> Option<&winit::window::Window>; + + fn save_and_destroy(&mut self); + + fn paint(&mut self) -> EventResult; + + fn on_event( + &mut self, + event_loop: &EventLoopWindowTarget, + event: &winit::event::Event<'_, UserEvent>, + ) -> Result; +} + +fn create_event_loop_builder( + native_options: &mut epi::NativeOptions, +) -> EventLoopBuilder { + let mut event_loop_builder = winit::event_loop::EventLoopBuilder::with_user_event(); + + if let Some(hook) = std::mem::take(&mut native_options.event_loop_builder) { + hook(&mut event_loop_builder); + } + + event_loop_builder +} + +/// Access a thread-local event loop. +/// +/// We reuse the event-loop so we can support closing and opening an eframe window +/// multiple times. This is just a limitation of winit. +fn with_event_loop( + mut native_options: epi::NativeOptions, + f: impl FnOnce(&mut EventLoop, NativeOptions) -> R, +) -> R { + use std::cell::RefCell; + thread_local!(static EVENT_LOOP: RefCell>> = RefCell::new(None)); + + EVENT_LOOP.with(|event_loop| { + // Since we want to reference NativeOptions when creating the EventLoop we can't + // do that as part of the lazy thread local storage initialization and so we instead + // create the event loop lazily here + let mut event_loop = event_loop.borrow_mut(); + let event_loop = event_loop + .get_or_insert_with(|| create_event_loop_builder(&mut native_options).build()); + f(event_loop, native_options) + }) +} + +fn run_and_return( + event_loop: &mut EventLoop, + mut winit_app: impl WinitApp, +) -> Result<()> { + use winit::platform::run_return::EventLoopExtRunReturn as _; + + tracing::debug!("Entering the winit event loop (run_return)…"); + + let mut next_repaint_time = Instant::now(); + + let mut returned_result = Ok(()); + + event_loop.run_return(|event, event_loop, control_flow| { + let event_result = match &event { + winit::event::Event::LoopDestroyed => { + // On Mac, Cmd-Q we get here and then `run_return` doesn't return (despite its name), + // so we need to save state now: + tracing::debug!("Received Event::LoopDestroyed - saving app state…"); + winit_app.save_and_destroy(); + *control_flow = ControlFlow::Exit; + return; + } + + // Platform-dependent event handlers to workaround a winit bug + // See: https://github.com/rust-windowing/winit/issues/987 + // See: https://github.com/rust-windowing/winit/issues/1619 + winit::event::Event::RedrawEventsCleared if cfg!(windows) => { + next_repaint_time = Instant::now() + Duration::from_secs(1_000_000_000); + winit_app.paint() + } + winit::event::Event::RedrawRequested(_) if !cfg!(windows) => { + next_repaint_time = Instant::now() + Duration::from_secs(1_000_000_000); + winit_app.paint() + } + + winit::event::Event::UserEvent(UserEvent::RequestRepaint) + | winit::event::Event::NewEvents(winit::event::StartCause::ResumeTimeReached { + .. + }) => EventResult::RepaintNext, + + winit::event::Event::WindowEvent { window_id, .. } + if winit_app.window().is_none() + || *window_id != winit_app.window().unwrap().id() => + { + // This can happen if we close a window, and then reopen a new one, + // or if we have multiple windows open. + EventResult::Wait + } + + event => match winit_app.on_event(event_loop, event) { + Ok(event_result) => event_result, + Err(err) => { + tracing::error!("Exiting because of error: {err:?} on event {event:?}"); + returned_result = Err(err); + EventResult::Exit + } + }, + }; + + match event_result { + EventResult::Wait => {} + EventResult::RepaintNow => { + tracing::trace!("Repaint caused by winit::Event: {:?}", event); + if cfg!(windows) { + // Fix flickering on Windows, see https://github.com/emilk/egui/pull/2280 + next_repaint_time = Instant::now() + Duration::from_secs(1_000_000_000); + winit_app.paint(); + } else { + // Fix for https://github.com/emilk/egui/issues/2425 + next_repaint_time = Instant::now(); + } + } + EventResult::RepaintNext => { + tracing::trace!("Repaint caused by winit::Event: {:?}", event); + next_repaint_time = Instant::now(); + } + EventResult::RepaintAt(repaint_time) => { + next_repaint_time = next_repaint_time.min(repaint_time); + } + EventResult::Exit => { + tracing::debug!("Asking to exit event loop…"); + *control_flow = ControlFlow::Exit; + return; + } + } + + *control_flow = match next_repaint_time.checked_duration_since(Instant::now()) { + None => { + if let Some(window) = winit_app.window() { + window.request_redraw(); + } + next_repaint_time = Instant::now() + Duration::from_secs(1_000_000_000); + ControlFlow::Poll + } + Some(time_until_next_repaint) => { + ControlFlow::WaitUntil(Instant::now() + time_until_next_repaint) + } + } + }); + + tracing::debug!("eframe window closed"); + + drop(winit_app); + + // On Windows this clears out events so that we can later create another window. + // See https://github.com/emilk/egui/pull/1889 for details. + event_loop.run_return(|_, _, control_flow| { + control_flow.set_exit(); + }); + + returned_result +} + +fn run_and_exit(event_loop: EventLoop, mut winit_app: impl WinitApp + 'static) -> ! { + tracing::debug!("Entering the winit event loop (run)…"); + + let mut next_repaint_time = Instant::now(); + + event_loop.run(move |event, event_loop, control_flow| { + let event_result = match event { + winit::event::Event::LoopDestroyed => { + tracing::debug!("Received Event::LoopDestroyed"); + EventResult::Exit + } + + // Platform-dependent event handlers to workaround a winit bug + // See: https://github.com/rust-windowing/winit/issues/987 + // See: https://github.com/rust-windowing/winit/issues/1619 + winit::event::Event::RedrawEventsCleared if cfg!(windows) => { + next_repaint_time = Instant::now() + Duration::from_secs(1_000_000_000); + winit_app.paint() + } + winit::event::Event::RedrawRequested(_) if !cfg!(windows) => { + next_repaint_time = Instant::now() + Duration::from_secs(1_000_000_000); + winit_app.paint() + } + + winit::event::Event::UserEvent(UserEvent::RequestRepaint) + | winit::event::Event::NewEvents(winit::event::StartCause::ResumeTimeReached { + .. + }) => EventResult::RepaintNext, + + event => match winit_app.on_event(event_loop, &event) { + Ok(event_result) => event_result, + Err(err) => { + panic!("eframe encountered a fatal error: {err}"); + } + }, + }; + + match event_result { + EventResult::Wait => {} + EventResult::RepaintNow => { + if cfg!(windows) { + // Fix flickering on Windows, see https://github.com/emilk/egui/pull/2280 + next_repaint_time = Instant::now() + Duration::from_secs(1_000_000_000); + winit_app.paint(); + } else { + // Fix for https://github.com/emilk/egui/issues/2425 + next_repaint_time = Instant::now(); + } + } + EventResult::RepaintNext => { + next_repaint_time = Instant::now(); + } + EventResult::RepaintAt(repaint_time) => { + next_repaint_time = next_repaint_time.min(repaint_time); + } + EventResult::Exit => { + tracing::debug!("Quitting - saving app state…"); + winit_app.save_and_destroy(); + #[allow(clippy::exit)] + std::process::exit(0); + } + } + + *control_flow = match next_repaint_time.checked_duration_since(Instant::now()) { + None => { + if let Some(window) = winit_app.window() { + window.request_redraw(); + } + ControlFlow::Poll + } + Some(time_until_next_repaint) => { + ControlFlow::WaitUntil(Instant::now() + time_until_next_repaint) + } + } + }) +} + +// ---------------------------------------------------------------------------- +/// Run an egui app +#[cfg(feature = "glow")] +mod glow_integration { + use std::sync::Arc; + + use egui::NumExt as _; + use glutin::{ + display::GetGlDisplay, + prelude::{GlDisplay, NotCurrentGlContextSurfaceAccessor, PossiblyCurrentGlContext}, + surface::GlSurface, + }; + use raw_window_handle::HasRawWindowHandle; + + use super::*; + + // Note: that the current Glutin API design tightly couples the GL context with + // the Window which means it's not practically possible to just destroy the + // window and re-create a new window while continuing to use the same GL context. + // + // For now this means it's not possible to support Android as well as we can with + // wgpu because we're basically forced to destroy and recreate _everything_ when + // the application suspends and resumes. + // + // There is work in progress to improve the Glutin API so it has a separate Surface + // API that would allow us to just destroy a Window/Surface when suspending, see: + // https://github.com/rust-windowing/glutin/pull/1435 + // + + /// State that is initialized when the application is first starts running via + /// a Resumed event. On Android this ensures that any graphics state is only + /// initialized once the application has an associated `SurfaceView`. + struct GlowWinitRunning { + gl: Arc, + painter: egui_glow::Painter, + integration: epi_integration::EpiIntegration, + app: Box, + // Conceptually this will be split out eventually so that the rest of the state + // can be persistent. + gl_window: GlutinWindowContext, + } + + /// This struct will contain both persistent and temporary glutin state. + /// + /// Platform Quirks: + /// * Microsoft Windows: requires that we create a window before opengl context. + /// * Android: window and surface should be destroyed when we receive a suspend event. recreate on resume event. + /// + /// winit guarantees that we will get a Resumed event on startup on all platforms. + /// * Before Resumed event: `gl_config`, `gl_context` can be created at any time. on windows, a window must be created to get `gl_context`. + /// * Resumed: `gl_surface` will be created here. `window` will be re-created here for android. + /// * Suspended: on android, we drop window + surface. on other platforms, we don't get Suspended event. + /// + /// The setup is divided between the `new` fn and `on_resume` fn. we can just assume that `on_resume` is a continuation of + /// `new` fn on all platforms. only on android, do we get multiple resumed events because app can be suspended. + struct GlutinWindowContext { + builder: winit::window::WindowBuilder, + swap_interval: glutin::surface::SwapInterval, + gl_config: glutin::config::Config, + current_gl_context: Option, + gl_surface: Option>, + not_current_gl_context: Option, + window: Option, + } + + impl GlutinWindowContext { + /// There is a lot of complexity with opengl creation, so prefer extensivve logging to get all the help we can to debug issues. + /// + #[allow(unsafe_code)] + unsafe fn new( + winit_window_builder: winit::window::WindowBuilder, + native_options: &epi::NativeOptions, + event_loop: &EventLoopWindowTarget, + ) -> Result { + use glutin::prelude::*; + // convert native options to glutin options + let hardware_acceleration = match native_options.hardware_acceleration { + crate::HardwareAcceleration::Required => Some(true), + crate::HardwareAcceleration::Preferred => None, + crate::HardwareAcceleration::Off => Some(false), + }; + let swap_interval = if native_options.vsync { + glutin::surface::SwapInterval::Wait(std::num::NonZeroU32::new(1).unwrap()) + } else { + glutin::surface::SwapInterval::DontWait + }; + /* opengl setup flow goes like this: + 1. we create a configuration for opengl "Display" / "Config" creation + 2. choose between special extensions like glx or egl or wgl and use them to create config/display + 3. opengl context configuration + 4. opengl context creation + */ + // start building config for gl display + let config_template_builder = glutin::config::ConfigTemplateBuilder::new() + .prefer_hardware_accelerated(hardware_acceleration) + .with_depth_size(native_options.depth_buffer) + .with_stencil_size(native_options.stencil_buffer) + .with_transparency(native_options.transparent); + // we don't know if multi sampling option is set. so, check if its more than 0. + let config_template_builder = if native_options.multisampling > 0 { + config_template_builder.with_multisampling( + native_options + .multisampling + .try_into() + .expect("failed to fit multisamples option of native_options into u8"), + ) + } else { + config_template_builder + }; + + tracing::debug!( + "trying to create glutin Display with config: {:?}", + &config_template_builder + ); + // create gl display. this may probably create a window too on most platforms. definitely on `MS windows`. never on android. + let (window, gl_config) = glutin_winit::DisplayBuilder::new() + // we might want to expose this option to users in the future. maybe using an env var or using native_options. + .with_preference(glutin_winit::ApiPrefence::FallbackEgl) // https://github.com/emilk/egui/issues/2520#issuecomment-1367841150 + .with_window_builder(Some(winit_window_builder.clone())) + .build( + event_loop, + config_template_builder.clone(), + |mut config_iterator| { + let config = config_iterator.next().expect( + "failed to find a matching configuration for creating glutin config", + ); + tracing::debug!( + "using the first config from config picker closure. config: {:?}", + &config + ); + config + }, + ) + .map_err(|e| crate::Error::NoGlutinConfigs(config_template_builder.build(), e))?; + + let gl_display = gl_config.display(); + tracing::debug!( + "successfully created GL Display with version: {} and supported features: {:?}", + gl_display.version_string(), + gl_display.supported_features() + ); + let raw_window_handle = window.as_ref().map(|w| w.raw_window_handle()); + tracing::debug!( + "creating gl context using raw window handle: {:?}", + raw_window_handle + ); + + // create gl context. if core context cannot be created, try gl es context as fallback. + let context_attributes = + glutin::context::ContextAttributesBuilder::new().build(raw_window_handle); + let fallback_context_attributes = glutin::context::ContextAttributesBuilder::new() + .with_context_api(glutin::context::ContextApi::Gles(None)) + .build(raw_window_handle); + let gl_context = match gl_config + .display() + .create_context(&gl_config, &context_attributes) + { + Ok(it) => it, + Err(err) => { + tracing::warn!("failed to create context using default context attributes {context_attributes:?} due to error: {err}"); + tracing::debug!("retrying with fallback context attributes: {fallback_context_attributes:?}"); + gl_config + .display() + .create_context(&gl_config, &fallback_context_attributes)? + } + }; + let not_current_gl_context = Some(gl_context); + + // the fun part with opengl gl is that we never know whether there is an error. the context creation might have failed, but + // it could keep working until we try to make surface current or swap buffers or something else. future glutin improvements might + // help us start from scratch again if we fail context creation and go back to preferEgl or try with different config etc.. + // https://github.com/emilk/egui/pull/2541#issuecomment-1370767582 + Ok(GlutinWindowContext { + builder: winit_window_builder, + swap_interval, + gl_config, + current_gl_context: None, + window, + gl_surface: None, + not_current_gl_context, + }) + } + + /// This will be run after `new`. on android, it might be called multiple times over the course of the app's lifetime. + /// roughly, + /// 1. check if window already exists. otherwise, create one now. + /// 2. create attributes for surface creation. + /// 3. create surface. + /// 4. make surface and context current. + /// + /// we presently assume that we will + #[allow(unsafe_code)] + fn on_resume(&mut self, event_loop: &EventLoopWindowTarget) -> Result<()> { + if self.gl_surface.is_some() { + tracing::warn!( + "on_resume called even thought we already have a surface. early return" + ); + return Ok(()); + } + tracing::debug!("running on_resume fn."); + // make sure we have a window or create one. + let window = self.window.take().unwrap_or_else(|| { + tracing::debug!("window doesn't exist yet. creating one now with finalize_window"); + glutin_winit::finalize_window(event_loop, self.builder.clone(), &self.gl_config) + .expect("failed to finalize glutin window") + }); + // surface attributes + let (width, height): (u32, u32) = window.inner_size().into(); + let width = std::num::NonZeroU32::new(width.at_least(1)).unwrap(); + let height = std::num::NonZeroU32::new(height.at_least(1)).unwrap(); + let surface_attributes = + glutin::surface::SurfaceAttributesBuilder::::new() + .build(window.raw_window_handle(), width, height); + tracing::debug!( + "creating surface with attributes: {:?}", + &surface_attributes + ); + // create surface + let gl_surface = unsafe { + self.gl_config + .display() + .create_window_surface(&self.gl_config, &surface_attributes)? + }; + tracing::debug!("surface created successfully: {gl_surface:?}.making context current"); + // make surface and context current. + let not_current_gl_context = self + .not_current_gl_context + .take() + .expect("failed to get not current context after resume event. impossible!"); + let current_gl_context = not_current_gl_context.make_current(&gl_surface)?; + // try setting swap interval. but its not absolutely necessary, so don't panic on failure. + tracing::debug!("made context current. setting swap interval for surface"); + if let Err(e) = gl_surface.set_swap_interval(¤t_gl_context, self.swap_interval) { + tracing::error!("failed to set swap interval due to error: {e:?}"); + } + // we will reach this point only once in most platforms except android. + // create window/surface/make context current once and just use them forever. + self.gl_surface = Some(gl_surface); + self.current_gl_context = Some(current_gl_context); + self.window = Some(window); + Ok(()) + } + + /// only applies for android. but we basically drop surface + window and make context not current + fn on_suspend(&mut self) -> Result<()> { + tracing::debug!("received suspend event. dropping window and surface"); + self.gl_surface.take(); + self.window.take(); + if let Some(current) = self.current_gl_context.take() { + tracing::debug!("context is current, so making it non-current"); + self.not_current_gl_context = Some(current.make_not_current()?); + } else { + tracing::debug!( + "context is already not current??? could be duplicate suspend event" + ); + } + Ok(()) + } + + fn window(&self) -> &winit::window::Window { + self.window.as_ref().expect("winit window doesn't exist") + } + + fn resize(&self, physical_size: winit::dpi::PhysicalSize) { + let width = std::num::NonZeroU32::new(physical_size.width.at_least(1)).unwrap(); + let height = std::num::NonZeroU32::new(physical_size.height.at_least(1)).unwrap(); + self.gl_surface + .as_ref() + .expect("failed to get surface to resize") + .resize( + self.current_gl_context + .as_ref() + .expect("failed to get current context to resize surface"), + width, + height, + ); + } + + fn swap_buffers(&self) -> glutin::error::Result<()> { + self.gl_surface + .as_ref() + .expect("failed to get surface to swap buffers") + .swap_buffers( + self.current_gl_context + .as_ref() + .expect("failed to get current context to swap buffers"), + ) + } + + fn get_proc_address(&self, addr: &std::ffi::CStr) -> *const std::ffi::c_void { + self.gl_config.display().get_proc_address(addr) + } + } + + struct GlowWinitApp { + repaint_proxy: Arc>>, + app_name: String, + native_options: epi::NativeOptions, + running: Option, + + // Note that since this `AppCreator` is FnOnce we are currently unable to support + // re-initializing the `GlowWinitRunning` state on Android if the application + // suspends and resumes. + app_creator: Option, + is_focused: bool, + + frame_nr: u64, + } + + impl GlowWinitApp { + fn new( + event_loop: &EventLoop, + app_name: &str, + native_options: epi::NativeOptions, + app_creator: epi::AppCreator, + ) -> Self { + Self { + repaint_proxy: Arc::new(egui::mutex::Mutex::new(event_loop.create_proxy())), + app_name: app_name.to_owned(), + native_options, + running: None, + app_creator: Some(app_creator), + is_focused: true, + frame_nr: 0, + } + } + + #[allow(unsafe_code)] + fn create_glutin_windowed_context( + event_loop: &EventLoopWindowTarget, + storage: Option<&dyn epi::Storage>, + title: &str, + native_options: &NativeOptions, + ) -> Result<(GlutinWindowContext, glow::Context)> { + crate::profile_function!(); + + let window_settings = epi_integration::load_window_settings(storage); + + let winit_window_builder = + epi_integration::window_builder(event_loop, title, native_options, window_settings); + let mut glutin_window_context = unsafe { + GlutinWindowContext::new(winit_window_builder, native_options, event_loop)? + }; + glutin_window_context.on_resume(event_loop)?; + + if let Some(window) = &glutin_window_context.window { + epi_integration::apply_native_options_to_window(window, native_options); + } + + let gl = unsafe { + glow::Context::from_loader_function(|s| { + let s = std::ffi::CString::new(s) + .expect("failed to construct C string from string for gl proc address"); + + glutin_window_context.get_proc_address(&s) + }) + }; + + Ok((glutin_window_context, gl)) + } + + fn init_run_state(&mut self, event_loop: &EventLoopWindowTarget) -> Result<()> { + let storage = epi_integration::create_storage(&self.app_name); + + let (gl_window, gl) = Self::create_glutin_windowed_context( + event_loop, + storage.as_deref(), + &self.app_name, + &self.native_options, + )?; + let gl = Arc::new(gl); + + let painter = + egui_glow::Painter::new(gl.clone(), "", self.native_options.shader_version) + .unwrap_or_else(|error| panic!("some OpenGL error occurred {}\n", error)); + + let system_theme = self.native_options.system_theme(); + let mut integration = epi_integration::EpiIntegration::new( + event_loop, + painter.max_texture_side(), + gl_window.window(), + system_theme, + storage, + Some(gl.clone()), + #[cfg(feature = "wgpu")] + None, + ); + #[cfg(feature = "accesskit")] + { + integration.init_accesskit(gl_window.window(), self.repaint_proxy.lock().clone()); + } + let theme = system_theme.unwrap_or(self.native_options.default_theme); + integration.egui_ctx.set_visuals(theme.egui_visuals()); + + gl_window.window().set_ime_allowed(true); + if self.native_options.mouse_passthrough { + gl_window.window().set_cursor_hittest(false).unwrap(); + } + + { + let event_loop_proxy = self.repaint_proxy.clone(); + integration.egui_ctx.set_request_repaint_callback(move || { + event_loop_proxy + .lock() + .send_event(UserEvent::RequestRepaint) + .ok(); + }); + } + + let app_creator = std::mem::take(&mut self.app_creator) + .expect("Single-use AppCreator has unexpectedly already been taken"); + let mut app = app_creator(&epi::CreationContext { + egui_ctx: integration.egui_ctx.clone(), + integration_info: integration.frame.info(), + storage: integration.frame.storage(), + gl: Some(gl.clone()), + #[cfg(feature = "wgpu")] + wgpu_render_state: None, + }); + + if app.warm_up_enabled() { + integration.warm_up(app.as_mut(), gl_window.window()); + } + + self.running = Some(GlowWinitRunning { + gl_window, + gl, + painter, + integration, + app, + }); + + Ok(()) + } + } + + impl WinitApp for GlowWinitApp { + fn is_focused(&self) -> bool { + self.is_focused + } + + fn integration(&self) -> Option<&EpiIntegration> { + self.running.as_ref().map(|r| &r.integration) + } + + fn window(&self) -> Option<&winit::window::Window> { + self.running.as_ref().map(|r| r.gl_window.window()) + } + + fn save_and_destroy(&mut self) { + if let Some(mut running) = self.running.take() { + running + .integration + .save(running.app.as_mut(), running.gl_window.window()); + running.app.on_exit(Some(&running.gl)); + running.painter.destroy(); + } + } + + fn paint(&mut self) -> EventResult { + if let Some(running) = &mut self.running { + #[cfg(feature = "puffin")] + puffin::GlobalProfiler::lock().new_frame(); + crate::profile_scope!("frame"); + + let GlowWinitRunning { + gl_window, + gl, + app, + integration, + painter, + } = running; + + let window = gl_window.window(); + + let screen_size_in_pixels: [u32; 2] = window.inner_size().into(); + + egui_glow::painter::clear( + gl, + screen_size_in_pixels, + app.clear_color(&integration.egui_ctx.style().visuals), + ); + + let egui::FullOutput { + platform_output, + repaint_after, + textures_delta, + shapes, + } = integration.update(app.as_mut(), window); + + integration.handle_platform_output(window, platform_output); + + let clipped_primitives = { + crate::profile_scope!("tessellate"); + integration.egui_ctx.tessellate(shapes) + }; + + painter.paint_and_update_textures( + screen_size_in_pixels, + integration.egui_ctx.pixels_per_point(), + &clipped_primitives, + &textures_delta, + ); + + integration.post_rendering(app.as_mut(), window); + + { + crate::profile_scope!("swap_buffers"); + gl_window.swap_buffers().unwrap(); + } + + integration.post_present(window); + + #[cfg(feature = "__screenshot")] + // give it time to settle: + if self.frame_nr == 2 { + if let Ok(path) = std::env::var("EFRAME_SCREENSHOT_TO") { + assert!( + path.ends_with(".png"), + "Expected EFRAME_SCREENSHOT_TO to end with '.png', got {path:?}" + ); + let [w, h] = screen_size_in_pixels; + let pixels = painter.read_screen_rgba(screen_size_in_pixels); + let image = image::RgbaImage::from_vec(w, h, pixels).unwrap(); + let image = image::imageops::flip_vertical(&image); + image.save(&path).unwrap_or_else(|err| { + panic!("Failed to save screenshot to {path:?}: {err}"); + }); + eprintln!("Screenshot saved to {path:?}."); + std::process::exit(0); + } + } + + let control_flow = if integration.should_close() { + EventResult::Exit + } else if repaint_after.is_zero() { + EventResult::RepaintNext + } else if let Some(repaint_after_instant) = + std::time::Instant::now().checked_add(repaint_after) + { + // if repaint_after is something huge and can't be added to Instant, + // we will use `ControlFlow::Wait` instead. + // technically, this might lead to some weird corner cases where the user *WANTS* + // winit to use `WaitUntil(MAX_INSTANT)` explicitly. they can roll their own + // egui backend impl i guess. + EventResult::RepaintAt(repaint_after_instant) + } else { + EventResult::Wait + }; + + integration.maybe_autosave(app.as_mut(), window); + + if !self.is_focused { + // On Mac, a minimized Window uses up all CPU: https://github.com/emilk/egui/issues/325 + // We can't know if we are minimized: https://github.com/rust-windowing/winit/issues/208 + // But we know if we are focused (in foreground). When minimized, we are not focused. + // However, a user may want an egui with an animation in the background, + // so we still need to repaint quite fast. + crate::profile_scope!("bg_sleep"); + std::thread::sleep(std::time::Duration::from_millis(10)); + } + + self.frame_nr += 1; + + control_flow + } else { + EventResult::Wait + } + } + + fn on_event( + &mut self, + event_loop: &EventLoopWindowTarget, + event: &winit::event::Event<'_, UserEvent>, + ) -> Result { + Ok(match event { + winit::event::Event::Resumed => { + // first resume event. + // we can actually move this outside of event loop. + // and just run the on_resume fn of gl_window + if self.running.is_none() { + self.init_run_state(event_loop)?; + } else { + // not the first resume event. create whatever you need. + self.running + .as_mut() + .unwrap() + .gl_window + .on_resume(event_loop)?; + } + EventResult::RepaintNow + } + winit::event::Event::Suspended => { + self.running.as_mut().unwrap().gl_window.on_suspend()?; + + EventResult::Wait + } + + winit::event::Event::WindowEvent { event, .. } => { + if let Some(running) = &mut self.running { + // On Windows, if a window is resized by the user, it should repaint synchronously, inside the + // event handler. + // + // If this is not done, the compositor will assume that the window does not want to redraw, + // and continue ahead. + // + // In eframe's case, that causes the window to rapidly flicker, as it struggles to deliver + // new frames to the compositor in time. + // + // The flickering is technically glutin or glow's fault, but we should be responding properly + // to resizes anyway, as doing so avoids dropping frames. + // + // See: https://github.com/emilk/egui/issues/903 + let mut repaint_asap = false; + + match &event { + winit::event::WindowEvent::Focused(new_focused) => { + self.is_focused = *new_focused; + } + winit::event::WindowEvent::Resized(physical_size) => { + repaint_asap = true; + + // Resize with 0 width and height is used by winit to signal a minimize event on Windows. + // See: https://github.com/rust-windowing/winit/issues/208 + // This solves an issue where the app would panic when minimizing on Windows. + if physical_size.width > 0 && physical_size.height > 0 { + running.gl_window.resize(*physical_size); + } + } + winit::event::WindowEvent::ScaleFactorChanged { + new_inner_size, + .. + } => { + repaint_asap = true; + running.gl_window.resize(**new_inner_size); + } + winit::event::WindowEvent::CloseRequested + if running.integration.should_close() => + { + tracing::debug!("Received WindowEvent::CloseRequested"); + return Ok(EventResult::Exit); + } + _ => {} + } + + let event_response = + running.integration.on_event(running.app.as_mut(), event); + + if running.integration.should_close() { + EventResult::Exit + } else if event_response.repaint { + if repaint_asap { + EventResult::RepaintNow + } else { + EventResult::RepaintNext + } + } else { + EventResult::Wait + } + } else { + EventResult::Wait + } + } + #[cfg(feature = "accesskit")] + winit::event::Event::UserEvent(UserEvent::AccessKitActionRequest( + accesskit_winit::ActionRequestEvent { request, .. }, + )) => { + if let Some(running) = &mut self.running { + running + .integration + .on_accesskit_action_request(request.clone()); + // As a form of user input, accessibility actions should + // lead to a repaint. + EventResult::RepaintNext + } else { + EventResult::Wait + } + } + _ => EventResult::Wait, + }) + } + } + + pub fn run_glow( + app_name: &str, + mut native_options: epi::NativeOptions, + app_creator: epi::AppCreator, + ) -> Result<()> { + if native_options.run_and_return { + with_event_loop(native_options, |event_loop, native_options| { + let glow_eframe = + GlowWinitApp::new(event_loop, app_name, native_options, app_creator); + run_and_return(event_loop, glow_eframe) + }) + } else { + let event_loop = create_event_loop_builder(&mut native_options).build(); + let glow_eframe = GlowWinitApp::new(&event_loop, app_name, native_options, app_creator); + run_and_exit(event_loop, glow_eframe); + } + } +} + +#[cfg(feature = "glow")] +pub use glow_integration::run_glow; +// ---------------------------------------------------------------------------- + +#[cfg(feature = "wgpu")] +mod wgpu_integration { + use std::sync::Arc; + + use super::*; + + /// State that is initialized when the application is first starts running via + /// a Resumed event. On Android this ensures that any graphics state is only + /// initialized once the application has an associated `SurfaceView`. + struct WgpuWinitRunning { + painter: egui_wgpu::winit::Painter, + integration: epi_integration::EpiIntegration, + app: Box, + } + + struct WgpuWinitApp { + repaint_proxy: Arc>>, + app_name: String, + native_options: epi::NativeOptions, + app_creator: Option, + running: Option, + + /// Window surface state that's initialized when the app starts running via a Resumed event + /// and on Android will also be destroyed if the application is paused. + window: Option, + is_focused: bool, + } + + impl WgpuWinitApp { + fn new( + event_loop: &EventLoop, + app_name: &str, + native_options: epi::NativeOptions, + app_creator: epi::AppCreator, + ) -> Self { + #[cfg(feature = "__screenshot")] + assert!( + std::env::var("EFRAME_SCREENSHOT_TO").is_err(), + "EFRAME_SCREENSHOT_TO not yet implemented for wgpu backend" + ); + + Self { + repaint_proxy: Arc::new(std::sync::Mutex::new(event_loop.create_proxy())), + app_name: app_name.to_owned(), + native_options, + running: None, + window: None, + app_creator: Some(app_creator), + is_focused: true, + } + } + + fn create_window( + event_loop: &EventLoopWindowTarget, + storage: Option<&dyn epi::Storage>, + title: &str, + native_options: &NativeOptions, + ) -> std::result::Result { + let window_settings = epi_integration::load_window_settings(storage); + let window_builder = + epi_integration::window_builder(event_loop, title, native_options, window_settings); + let window = window_builder.build(event_loop)?; + epi_integration::apply_native_options_to_window(&window, native_options); + Ok(window) + } + + #[allow(unsafe_code)] + fn set_window( + &mut self, + window: winit::window::Window, + ) -> std::result::Result<(), egui_wgpu::WgpuError> { + self.window = Some(window); + if let Some(running) = &mut self.running { + unsafe { + pollster::block_on(running.painter.set_window(self.window.as_ref()))?; + } + } + Ok(()) + } + + #[allow(unsafe_code)] + #[cfg(target_os = "android")] + fn drop_window(&mut self) -> std::result::Result<(), egui_wgpu::WgpuError> { + self.window = None; + if let Some(running) = &mut self.running { + unsafe { + pollster::block_on(running.painter.set_window(None))?; + } + } + Ok(()) + } + + fn init_run_state( + &mut self, + event_loop: &EventLoopWindowTarget, + storage: Option>, + window: winit::window::Window, + ) -> std::result::Result<(), egui_wgpu::WgpuError> { + #[allow(unsafe_code, unused_mut, unused_unsafe)] + let painter = unsafe { + let mut painter = egui_wgpu::winit::Painter::new( + self.native_options.wgpu_options.clone(), + self.native_options.multisampling.max(1) as _, + self.native_options.depth_buffer, + self.native_options.transparent, + ); + pollster::block_on(painter.set_window(Some(&window)))?; + painter + }; + + let wgpu_render_state = painter.render_state(); + + let system_theme = self.native_options.system_theme(); + let mut integration = epi_integration::EpiIntegration::new( + event_loop, + painter.max_texture_side().unwrap_or(2048), + &window, + system_theme, + storage, + #[cfg(feature = "glow")] + None, + wgpu_render_state.clone(), + ); + #[cfg(feature = "accesskit")] + { + integration.init_accesskit(&window, self.repaint_proxy.lock().unwrap().clone()); + } + let theme = system_theme.unwrap_or(self.native_options.default_theme); + integration.egui_ctx.set_visuals(theme.egui_visuals()); + + window.set_ime_allowed(true); + + { + let event_loop_proxy = self.repaint_proxy.clone(); + integration.egui_ctx.set_request_repaint_callback(move || { + event_loop_proxy + .lock() + .unwrap() + .send_event(UserEvent::RequestRepaint) + .ok(); + }); + } + + let app_creator = std::mem::take(&mut self.app_creator) + .expect("Single-use AppCreator has unexpectedly already been taken"); + let mut app = app_creator(&epi::CreationContext { + egui_ctx: integration.egui_ctx.clone(), + integration_info: integration.frame.info(), + storage: integration.frame.storage(), + #[cfg(feature = "glow")] + gl: None, + wgpu_render_state, + }); + + if app.warm_up_enabled() { + integration.warm_up(app.as_mut(), &window); + } + + self.running = Some(WgpuWinitRunning { + painter, + integration, + app, + }); + self.window = Some(window); + + Ok(()) + } + } + + impl WinitApp for WgpuWinitApp { + fn is_focused(&self) -> bool { + self.is_focused + } + + fn integration(&self) -> Option<&EpiIntegration> { + self.running.as_ref().map(|r| &r.integration) + } + + fn window(&self) -> Option<&winit::window::Window> { + self.window.as_ref() + } + + fn save_and_destroy(&mut self) { + if let Some(mut running) = self.running.take() { + if let Some(window) = &self.window { + running.integration.save(running.app.as_mut(), window); + } + + #[cfg(feature = "glow")] + running.app.on_exit(None); + + #[cfg(not(feature = "glow"))] + running.app.on_exit(); + + running.painter.destroy(); + } + } + + fn paint(&mut self) -> EventResult { + if let (Some(running), Some(window)) = (&mut self.running, &self.window) { + #[cfg(feature = "puffin")] + puffin::GlobalProfiler::lock().new_frame(); + crate::profile_scope!("frame"); + + let WgpuWinitRunning { + app, + integration, + painter, + } = running; + + let egui::FullOutput { + platform_output, + repaint_after, + textures_delta, + shapes, + } = integration.update(app.as_mut(), window); + + integration.handle_platform_output(window, platform_output); + + let clipped_primitives = { + crate::profile_scope!("tessellate"); + integration.egui_ctx.tessellate(shapes) + }; + + painter.paint_and_update_textures( + integration.egui_ctx.pixels_per_point(), + app.clear_color(&integration.egui_ctx.style().visuals), + &clipped_primitives, + &textures_delta, + ); + + integration.post_rendering(app.as_mut(), window); + integration.post_present(window); + + let control_flow = if integration.should_close() { + EventResult::Exit + } else if repaint_after.is_zero() { + EventResult::RepaintNext + } else if let Some(repaint_after_instant) = + std::time::Instant::now().checked_add(repaint_after) + { + // if repaint_after is something huge and can't be added to Instant, + // we will use `ControlFlow::Wait` instead. + // technically, this might lead to some weird corner cases where the user *WANTS* + // winit to use `WaitUntil(MAX_INSTANT)` explicitly. they can roll their own + // egui backend impl i guess. + EventResult::RepaintAt(repaint_after_instant) + } else { + EventResult::Wait + }; + + integration.maybe_autosave(app.as_mut(), window); + + if !self.is_focused { + // On Mac, a minimized Window uses up all CPU: https://github.com/emilk/egui/issues/325 + // We can't know if we are minimized: https://github.com/rust-windowing/winit/issues/208 + // But we know if we are focused (in foreground). When minimized, we are not focused. + // However, a user may want an egui with an animation in the background, + // so we still need to repaint quite fast. + crate::profile_scope!("bg_sleep"); + std::thread::sleep(std::time::Duration::from_millis(10)); + } + + control_flow + } else { + EventResult::Wait + } + } + + fn on_event( + &mut self, + event_loop: &EventLoopWindowTarget, + event: &winit::event::Event<'_, UserEvent>, + ) -> Result { + Ok(match event { + winit::event::Event::Resumed => { + if let Some(running) = &self.running { + if self.window.is_none() { + let window = Self::create_window( + event_loop, + running.integration.frame.storage(), + &self.app_name, + &self.native_options, + )?; + self.set_window(window)?; + } + } else { + let storage = epi_integration::create_storage(&self.app_name); + let window = Self::create_window( + event_loop, + storage.as_deref(), + &self.app_name, + &self.native_options, + )?; + self.init_run_state(event_loop, storage, window)?; + } + EventResult::RepaintNow + } + winit::event::Event::Suspended => { + #[cfg(target_os = "android")] + self.drop_window()?; + EventResult::Wait + } + + winit::event::Event::WindowEvent { event, .. } => { + if let Some(running) = &mut self.running { + // On Windows, if a window is resized by the user, it should repaint synchronously, inside the + // event handler. + // + // If this is not done, the compositor will assume that the window does not want to redraw, + // and continue ahead. + // + // In eframe's case, that causes the window to rapidly flicker, as it struggles to deliver + // new frames to the compositor in time. + // + // The flickering is technically glutin or glow's fault, but we should be responding properly + // to resizes anyway, as doing so avoids dropping frames. + // + // See: https://github.com/emilk/egui/issues/903 + let mut repaint_asap = false; + + match &event { + winit::event::WindowEvent::Focused(new_focused) => { + self.is_focused = *new_focused; + } + winit::event::WindowEvent::Resized(physical_size) => { + repaint_asap = true; + + // Resize with 0 width and height is used by winit to signal a minimize event on Windows. + // See: https://github.com/rust-windowing/winit/issues/208 + // This solves an issue where the app would panic when minimizing on Windows. + if physical_size.width > 0 && physical_size.height > 0 { + running.painter.on_window_resized( + physical_size.width, + physical_size.height, + ); + } + } + winit::event::WindowEvent::ScaleFactorChanged { + new_inner_size, + .. + } => { + repaint_asap = true; + running + .painter + .on_window_resized(new_inner_size.width, new_inner_size.height); + } + winit::event::WindowEvent::CloseRequested + if running.integration.should_close() => + { + tracing::debug!("Received WindowEvent::CloseRequested"); + return Ok(EventResult::Exit); + } + _ => {} + }; + + let event_response = + running.integration.on_event(running.app.as_mut(), event); + if running.integration.should_close() { + EventResult::Exit + } else if event_response.repaint { + if repaint_asap { + EventResult::RepaintNow + } else { + EventResult::RepaintNext + } + } else { + EventResult::Wait + } + } else { + EventResult::Wait + } + } + #[cfg(feature = "accesskit")] + winit::event::Event::UserEvent(UserEvent::AccessKitActionRequest( + accesskit_winit::ActionRequestEvent { request, .. }, + )) => { + if let Some(running) = &mut self.running { + running + .integration + .on_accesskit_action_request(request.clone()); + // As a form of user input, accessibility actions should + // lead to a repaint. + EventResult::RepaintNext + } else { + EventResult::Wait + } + } + _ => EventResult::Wait, + }) + } + } + + pub fn run_wgpu( + app_name: &str, + mut native_options: epi::NativeOptions, + app_creator: epi::AppCreator, + ) -> Result<()> { + if native_options.run_and_return { + with_event_loop(native_options, |event_loop, native_options| { + let wgpu_eframe = + WgpuWinitApp::new(event_loop, app_name, native_options, app_creator); + run_and_return(event_loop, wgpu_eframe) + }) + } else { + let event_loop = create_event_loop_builder(&mut native_options).build(); + let wgpu_eframe = WgpuWinitApp::new(&event_loop, app_name, native_options, app_creator); + run_and_exit(event_loop, wgpu_eframe); + } + } +} + +// ---------------------------------------------------------------------------- + +#[cfg(feature = "wgpu")] +pub use wgpu_integration::run_wgpu; diff --git a/nevmes-gui/crates/eframe/src/web/backend.rs b/nevmes-gui/crates/eframe/src/web/backend.rs new file mode 100644 index 0000000..76d05af --- /dev/null +++ b/nevmes-gui/crates/eframe/src/web/backend.rs @@ -0,0 +1,585 @@ +use egui::{ + mutex::{Mutex, MutexGuard}, + TexturesDelta, +}; + +use crate::{epi, App}; + +use super::{web_painter::WebPainter, *}; + +// ---------------------------------------------------------------------------- + +/// Data gathered between frames. +#[derive(Default)] +pub struct WebInput { + /// Required because we don't get a position on touched + pub latest_touch_pos: Option, + + /// Required to maintain a stable touch position for multi-touch gestures. + pub latest_touch_pos_id: Option, + + pub raw: egui::RawInput, +} + +impl WebInput { + pub fn new_frame(&mut self, canvas_size: egui::Vec2) -> egui::RawInput { + egui::RawInput { + screen_rect: Some(egui::Rect::from_min_size(Default::default(), canvas_size)), + pixels_per_point: Some(native_pixels_per_point()), // We ALWAYS use the native pixels-per-point + time: Some(now_sec()), + ..self.raw.take() + } + } +} + +// ---------------------------------------------------------------------------- + +use std::sync::atomic::Ordering::SeqCst; + +/// Stores when to do the next repaint. +pub struct NeedRepaint(Mutex); + +impl Default for NeedRepaint { + fn default() -> Self { + Self(Mutex::new(f64::NEG_INFINITY)) // start with a repaint + } +} + +impl NeedRepaint { + /// Returns the time (in [`now_sec`] scale) when + /// we should next repaint. + pub fn when_to_repaint(&self) -> f64 { + *self.0.lock() + } + + /// Unschedule repainting. + pub fn clear(&self) { + *self.0.lock() = f64::INFINITY; + } + + pub fn repaint_after(&self, num_seconds: f64) { + let mut repaint_time = self.0.lock(); + *repaint_time = repaint_time.min(now_sec() + num_seconds); + } + + pub fn repaint_asap(&self) { + *self.0.lock() = f64::NEG_INFINITY; + } +} + +pub struct IsDestroyed(std::sync::atomic::AtomicBool); + +impl Default for IsDestroyed { + fn default() -> Self { + Self(false.into()) + } +} + +impl IsDestroyed { + pub fn fetch(&self) -> bool { + self.0.load(SeqCst) + } + + pub fn set_true(&self) { + self.0.store(true, SeqCst); + } +} + +// ---------------------------------------------------------------------------- + +fn user_agent() -> Option { + web_sys::window()?.navigator().user_agent().ok() +} + +fn web_location() -> epi::Location { + let location = web_sys::window().unwrap().location(); + + let hash = percent_decode(&location.hash().unwrap_or_default()); + + let query = location + .search() + .unwrap_or_default() + .strip_prefix('?') + .map(percent_decode) + .unwrap_or_default(); + + let query_map = parse_query_map(&query) + .iter() + .map(|(k, v)| ((*k).to_owned(), (*v).to_owned())) + .collect(); + + epi::Location { + url: percent_decode(&location.href().unwrap_or_default()), + protocol: percent_decode(&location.protocol().unwrap_or_default()), + host: percent_decode(&location.host().unwrap_or_default()), + hostname: percent_decode(&location.hostname().unwrap_or_default()), + port: percent_decode(&location.port().unwrap_or_default()), + hash, + query, + query_map, + origin: percent_decode(&location.origin().unwrap_or_default()), + } +} + +fn parse_query_map(query: &str) -> BTreeMap<&str, &str> { + query + .split('&') + .filter_map(|pair| { + if pair.is_empty() { + None + } else { + Some(if let Some((key, value)) = pair.split_once('=') { + (key, value) + } else { + (pair, "") + }) + } + }) + .collect() +} + +#[test] +fn test_parse_query() { + assert_eq!(parse_query_map(""), BTreeMap::default()); + assert_eq!(parse_query_map("foo"), BTreeMap::from_iter([("foo", "")])); + assert_eq!( + parse_query_map("foo=bar"), + BTreeMap::from_iter([("foo", "bar")]) + ); + assert_eq!( + parse_query_map("foo=bar&baz=42"), + BTreeMap::from_iter([("foo", "bar"), ("baz", "42")]) + ); + assert_eq!( + parse_query_map("foo&baz=42"), + BTreeMap::from_iter([("foo", ""), ("baz", "42")]) + ); + assert_eq!( + parse_query_map("foo&baz&&"), + BTreeMap::from_iter([("foo", ""), ("baz", "")]) + ); +} + +// ---------------------------------------------------------------------------- + +pub struct AppRunner { + pub(crate) frame: epi::Frame, + egui_ctx: egui::Context, + painter: ActiveWebPainter, + pub(crate) input: WebInput, + app: Box, + pub(crate) needs_repaint: std::sync::Arc, + pub(crate) is_destroyed: std::sync::Arc, + last_save_time: f64, + screen_reader: super::screen_reader::ScreenReader, + pub(crate) text_cursor_pos: Option, + pub(crate) mutable_text_under_cursor: bool, + textures_delta: TexturesDelta, + pub events_to_unsubscribe: Vec, +} + +impl Drop for AppRunner { + fn drop(&mut self) { + tracing::debug!("AppRunner has fully dropped"); + } +} + +impl AppRunner { + /// # Errors + /// Failure to initialize WebGL renderer. + pub async fn new( + canvas_id: &str, + web_options: crate::WebOptions, + app_creator: epi::AppCreator, + ) -> Result { + let painter = ActiveWebPainter::new(canvas_id, &web_options).await?; + + let system_theme = if web_options.follow_system_theme { + super::system_theme() + } else { + None + }; + + let info = epi::IntegrationInfo { + web_info: epi::WebInfo { + user_agent: user_agent().unwrap_or_default(), + location: web_location(), + }, + system_theme, + cpu_usage: None, + native_pixels_per_point: Some(native_pixels_per_point()), + }; + let storage = LocalStorage::default(); + + let egui_ctx = egui::Context::default(); + egui_ctx.set_os(egui::os::OperatingSystem::from_user_agent( + &user_agent().unwrap_or_default(), + )); + load_memory(&egui_ctx); + + let theme = system_theme.unwrap_or(web_options.default_theme); + egui_ctx.set_visuals(theme.egui_visuals()); + + let app = app_creator(&epi::CreationContext { + egui_ctx: egui_ctx.clone(), + integration_info: info.clone(), + storage: Some(&storage), + + #[cfg(feature = "glow")] + gl: Some(painter.gl().clone()), + + #[cfg(all(feature = "wgpu", not(feature = "glow")))] + wgpu_render_state: painter.render_state(), + #[cfg(all(feature = "wgpu", feature = "glow"))] + wgpu_render_state: None, + }); + + let frame = epi::Frame { + info, + output: Default::default(), + storage: Some(Box::new(storage)), + + #[cfg(feature = "glow")] + gl: Some(painter.gl().clone()), + + #[cfg(all(feature = "wgpu", not(feature = "glow")))] + wgpu_render_state: painter.render_state(), + #[cfg(all(feature = "wgpu", feature = "glow"))] + wgpu_render_state: None, + }; + + let needs_repaint: std::sync::Arc = Default::default(); + { + let needs_repaint = needs_repaint.clone(); + egui_ctx.set_request_repaint_callback(move || { + needs_repaint.repaint_asap(); + }); + } + + let mut runner = Self { + frame, + egui_ctx, + painter, + input: Default::default(), + app, + needs_repaint, + is_destroyed: Default::default(), + last_save_time: now_sec(), + screen_reader: Default::default(), + text_cursor_pos: None, + mutable_text_under_cursor: false, + textures_delta: Default::default(), + events_to_unsubscribe: Default::default(), + }; + + runner.input.raw.max_texture_side = Some(runner.painter.max_texture_side()); + + Ok(runner) + } + + pub fn egui_ctx(&self) -> &egui::Context { + &self.egui_ctx + } + + /// Get mutable access to the concrete [`App`] we enclose. + /// + /// This will panic if your app does not implement [`App::as_any_mut`]. + pub fn app_mut(&mut self) -> &mut ConreteApp { + self.app + .as_any_mut() + .expect("Your app must implement `as_any_mut`, but it doesn't") + .downcast_mut::() + .unwrap() + } + + pub fn auto_save(&mut self) { + let now = now_sec(); + let time_since_last_save = now - self.last_save_time; + + if time_since_last_save > self.app.auto_save_interval().as_secs_f64() { + if self.app.persist_egui_memory() { + save_memory(&self.egui_ctx); + } + if let Some(storage) = self.frame.storage_mut() { + self.app.save(storage); + } + self.last_save_time = now; + } + } + + pub fn canvas_id(&self) -> &str { + self.painter.canvas_id() + } + + pub fn warm_up(&mut self) -> Result<(), JsValue> { + if self.app.warm_up_enabled() { + let saved_memory: egui::Memory = self.egui_ctx.memory(|m| m.clone()); + self.egui_ctx + .memory_mut(|m| m.set_everything_is_visible(true)); + self.logic()?; + self.egui_ctx.memory_mut(|m| *m = saved_memory); // We don't want to remember that windows were huge. + self.egui_ctx.clear_animations(); + } + Ok(()) + } + + pub fn destroy(&mut self) -> Result<(), JsValue> { + let is_destroyed_already = self.is_destroyed.fetch(); + + if is_destroyed_already { + tracing::warn!("App was destroyed already"); + Ok(()) + } else { + tracing::debug!("Destroying"); + for x in self.events_to_unsubscribe.drain(..) { + x.unsubscribe()?; + } + + self.painter.destroy(); + self.is_destroyed.set_true(); + Ok(()) + } + } + + /// Returns how long to wait until the next repaint. + /// + /// Call [`Self::paint`] later to paint + pub fn logic(&mut self) -> Result<(std::time::Duration, Vec), JsValue> { + let frame_start = now_sec(); + + resize_canvas_to_screen_size(self.canvas_id(), self.app.max_size_points()); + let canvas_size = canvas_size_in_points(self.canvas_id()); + let raw_input = self.input.new_frame(canvas_size); + + let full_output = self.egui_ctx.run(raw_input, |egui_ctx| { + self.app.update(egui_ctx, &mut self.frame); + }); + let egui::FullOutput { + platform_output, + repaint_after, + textures_delta, + shapes, + } = full_output; + + self.handle_platform_output(platform_output); + self.textures_delta.append(textures_delta); + let clipped_primitives = self.egui_ctx.tessellate(shapes); + + { + let app_output = self.frame.take_app_output(); + let epi::backend::AppOutput {} = app_output; + } + + self.frame.info.cpu_usage = Some((now_sec() - frame_start) as f32); + Ok((repaint_after, clipped_primitives)) + } + + /// Paint the results of the last call to [`Self::logic`]. + pub fn paint(&mut self, clipped_primitives: &[egui::ClippedPrimitive]) -> Result<(), JsValue> { + let textures_delta = std::mem::take(&mut self.textures_delta); + + self.painter.paint_and_update_textures( + self.app.clear_color(&self.egui_ctx.style().visuals), + clipped_primitives, + self.egui_ctx.pixels_per_point(), + &textures_delta, + )?; + + Ok(()) + } + + fn handle_platform_output(&mut self, platform_output: egui::PlatformOutput) { + if self.egui_ctx.options(|o| o.screen_reader) { + self.screen_reader + .speak(&platform_output.events_description()); + } + + let egui::PlatformOutput { + cursor_icon, + open_url, + copied_text, + events: _, // already handled + mutable_text_under_cursor, + text_cursor_pos, + #[cfg(feature = "accesskit")] + accesskit_update: _, // not currently implemented + } = platform_output; + + set_cursor_icon(cursor_icon); + if let Some(open) = open_url { + super::open_url(&open.url, open.new_tab); + } + + #[cfg(web_sys_unstable_apis)] + if !copied_text.is_empty() { + set_clipboard_text(&copied_text); + } + + #[cfg(not(web_sys_unstable_apis))] + let _ = copied_text; + + self.mutable_text_under_cursor = mutable_text_under_cursor; + + if self.text_cursor_pos != text_cursor_pos { + text_agent::move_text_cursor(text_cursor_pos, self.canvas_id()); + self.text_cursor_pos = text_cursor_pos; + } + } +} + +// ---------------------------------------------------------------------------- + +pub type AppRunnerRef = Arc>; + +pub struct TargetEvent { + target: EventTarget, + event_name: String, + closure: Closure, +} + +pub struct IntervalHandle { + pub handle: i32, + pub closure: Closure, +} + +pub enum EventToUnsubscribe { + TargetEvent(TargetEvent), + #[allow(dead_code)] + IntervalHandle(IntervalHandle), +} + +impl EventToUnsubscribe { + pub fn unsubscribe(self) -> Result<(), JsValue> { + match self { + EventToUnsubscribe::TargetEvent(handle) => { + handle.target.remove_event_listener_with_callback( + handle.event_name.as_str(), + handle.closure.as_ref().unchecked_ref(), + )?; + Ok(()) + } + EventToUnsubscribe::IntervalHandle(handle) => { + let window = web_sys::window().unwrap(); + window.clear_interval_with_handle(handle.handle); + Ok(()) + } + } + } +} + +pub struct AppRunnerContainer { + pub runner: AppRunnerRef, + + /// Set to `true` if there is a panic. + /// Used to ignore callbacks after a panic. + pub panicked: Arc, + pub events: Vec, +} + +impl AppRunnerContainer { + /// Convenience function to reduce boilerplate and ensure that all event handlers + /// are dealt with in the same way + pub fn add_event_listener( + &mut self, + target: &EventTarget, + event_name: &'static str, + mut closure: impl FnMut(E, MutexGuard<'_, AppRunner>) + 'static, + ) -> Result<(), JsValue> { + // Create a JS closure based on the FnMut provided + let closure = Closure::wrap({ + // Clone atomics + let runner_ref = self.runner.clone(); + let panicked = self.panicked.clone(); + + Box::new(move |event: web_sys::Event| { + // Only call the wrapped closure if the egui code has not panicked + if !panicked.load(Ordering::SeqCst) { + // Cast the event to the expected event type + let event = event.unchecked_into::(); + + closure(event, runner_ref.lock()); + } + }) as Box + }); + + // Add the event listener to the target + target.add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref())?; + + let handle = TargetEvent { + target: target.clone(), + event_name: event_name.to_owned(), + closure, + }; + + self.events.push(EventToUnsubscribe::TargetEvent(handle)); + + Ok(()) + } +} + +// ---------------------------------------------------------------------------- + +/// Install event listeners to register different input events +/// and start running the given app. +pub async fn start( + canvas_id: &str, + web_options: crate::WebOptions, + app_creator: epi::AppCreator, +) -> Result { + #[cfg(not(web_sys_unstable_apis))] + tracing::warn!( + "eframe compiled without RUSTFLAGS='--cfg=web_sys_unstable_apis'. Copying text won't work." + ); + + let mut runner = AppRunner::new(canvas_id, web_options, app_creator).await?; + runner.warm_up()?; + start_runner(runner) +} + +/// Install event listeners to register different input events +/// and starts running the given [`AppRunner`]. +fn start_runner(app_runner: AppRunner) -> Result { + let mut runner_container = AppRunnerContainer { + runner: Arc::new(Mutex::new(app_runner)), + panicked: Arc::new(AtomicBool::new(false)), + events: Vec::with_capacity(20), + }; + + super::events::install_canvas_events(&mut runner_container)?; + super::events::install_document_events(&mut runner_container)?; + text_agent::install_text_agent(&mut runner_container)?; + + super::events::paint_and_schedule(&runner_container.runner, runner_container.panicked.clone())?; + + // Disable all event handlers on panic + let previous_hook = std::panic::take_hook(); + + runner_container.runner.lock().events_to_unsubscribe = runner_container.events; + + std::panic::set_hook(Box::new(move |panic_info| { + tracing::info!("egui disabled all event handlers due to panic"); + runner_container.panicked.store(true, SeqCst); + + // Propagate panic info to the previously registered panic hook + previous_hook(panic_info); + })); + + Ok(runner_container.runner) +} + +// ---------------------------------------------------------------------------- + +#[derive(Default)] +struct LocalStorage {} + +impl epi::Storage for LocalStorage { + fn get_string(&self, key: &str) -> Option { + local_storage_get(key) + } + + fn set_string(&mut self, key: &str, value: String) { + local_storage_set(key, &value); + } + + fn flush(&mut self) {} +} diff --git a/nevmes-gui/crates/eframe/src/web/events.rs b/nevmes-gui/crates/eframe/src/web/events.rs new file mode 100644 index 0000000..d85adaa --- /dev/null +++ b/nevmes-gui/crates/eframe/src/web/events.rs @@ -0,0 +1,538 @@ +use std::sync::atomic::{AtomicBool, Ordering}; + +use egui::Key; + +use super::*; + +struct IsDestroyed(pub bool); + +pub fn paint_and_schedule( + runner_ref: &AppRunnerRef, + panicked: Arc, +) -> Result<(), JsValue> { + fn paint_if_needed(runner_ref: &AppRunnerRef) -> Result { + let mut runner_lock = runner_ref.lock(); + let is_destroyed = runner_lock.is_destroyed.fetch(); + + if !is_destroyed && runner_lock.needs_repaint.when_to_repaint() <= now_sec() { + runner_lock.needs_repaint.clear(); + let (repaint_after, clipped_primitives) = runner_lock.logic()?; + runner_lock.paint(&clipped_primitives)?; + runner_lock + .needs_repaint + .repaint_after(repaint_after.as_secs_f64()); + runner_lock.auto_save(); + } + + Ok(IsDestroyed(is_destroyed)) + } + + fn request_animation_frame( + runner_ref: AppRunnerRef, + panicked: Arc, + ) -> Result<(), JsValue> { + let window = web_sys::window().unwrap(); + let closure = Closure::once(move || paint_and_schedule(&runner_ref, panicked)); + window.request_animation_frame(closure.as_ref().unchecked_ref())?; + closure.forget(); // We must forget it, or else the callback is canceled on drop + Ok(()) + } + + // Only paint and schedule if there has been no panic + if !panicked.load(Ordering::SeqCst) { + let is_destroyed = paint_if_needed(runner_ref)?; + if !is_destroyed.0 { + request_animation_frame(runner_ref.clone(), panicked)?; + } + } + + Ok(()) +} + +pub fn install_document_events(runner_container: &mut AppRunnerContainer) -> Result<(), JsValue> { + let window = web_sys::window().unwrap(); + let document = window.document().unwrap(); + + runner_container.add_event_listener( + &document, + "keydown", + |event: web_sys::KeyboardEvent, mut runner_lock| { + if event.is_composing() || event.key_code() == 229 { + // https://web.archive.org/web/20200526195704/https://www.fxsitecompat.dev/en-CA/docs/2018/keydown-and-keyup-events-are-now-fired-during-ime-composition/ + return; + } + + let modifiers = modifiers_from_event(&event); + runner_lock.input.raw.modifiers = modifiers; + + let key = event.key(); + let egui_key = translate_key(&key); + + if let Some(key) = egui_key { + runner_lock.input.raw.events.push(egui::Event::Key { + key, + pressed: true, + repeat: false, // egui will fill this in for us! + modifiers, + }); + } + if !modifiers.ctrl + && !modifiers.command + && !should_ignore_key(&key) + // When text agent is shown, it sends text event instead. + && text_agent::text_agent().hidden() + { + runner_lock.input.raw.events.push(egui::Event::Text(key)); + } + runner_lock.needs_repaint.repaint_asap(); + + let egui_wants_keyboard = runner_lock.egui_ctx().wants_keyboard_input(); + + #[allow(clippy::if_same_then_else)] + let prevent_default = if egui_key == Some(Key::Tab) { + // Always prevent moving cursor to url bar. + // egui wants to use tab to move to the next text field. + true + } else if egui_key == Some(Key::P) { + #[allow(clippy::needless_bool)] + if modifiers.ctrl || modifiers.command || modifiers.mac_cmd { + true // Prevent ctrl-P opening the print dialog. Users may want to use it for a command palette. + } else { + false // let normal P:s through + } + } else if egui_wants_keyboard { + matches!( + event.key().as_str(), + "Backspace" // so we don't go back to previous page when deleting text + | "ArrowDown" | "ArrowLeft" | "ArrowRight" | "ArrowUp" // cmd-left is "back" on Mac (https://github.com/emilk/egui/issues/58) + ) + } else { + // We never want to prevent: + // * F5 / cmd-R (refresh) + // * cmd-shift-C (debug tools) + // * cmd/ctrl-c/v/x (or we stop copy/past/cut events) + false + }; + + // tracing::debug!( + // "On key-down {:?}, egui_wants_keyboard: {}, prevent_default: {}", + // event.key().as_str(), + // egui_wants_keyboard, + // prevent_default + // ); + + if prevent_default { + event.prevent_default(); + // event.stop_propagation(); + } + }, + )?; + + runner_container.add_event_listener( + &document, + "keyup", + |event: web_sys::KeyboardEvent, mut runner_lock| { + let modifiers = modifiers_from_event(&event); + runner_lock.input.raw.modifiers = modifiers; + if let Some(key) = translate_key(&event.key()) { + runner_lock.input.raw.events.push(egui::Event::Key { + key, + pressed: false, + repeat: false, + modifiers, + }); + } + runner_lock.needs_repaint.repaint_asap(); + }, + )?; + + #[cfg(web_sys_unstable_apis)] + runner_container.add_event_listener( + &document, + "paste", + |event: web_sys::ClipboardEvent, mut runner_lock| { + if let Some(data) = event.clipboard_data() { + if let Ok(text) = data.get_data("text") { + let text = text.replace("\r\n", "\n"); + if !text.is_empty() { + runner_lock.input.raw.events.push(egui::Event::Paste(text)); + runner_lock.needs_repaint.repaint_asap(); + } + event.stop_propagation(); + event.prevent_default(); + } + } + }, + )?; + + #[cfg(web_sys_unstable_apis)] + runner_container.add_event_listener( + &document, + "cut", + |_: web_sys::ClipboardEvent, mut runner_lock| { + runner_lock.input.raw.events.push(egui::Event::Cut); + runner_lock.needs_repaint.repaint_asap(); + }, + )?; + + #[cfg(web_sys_unstable_apis)] + runner_container.add_event_listener( + &document, + "copy", + |_: web_sys::ClipboardEvent, mut runner_lock| { + runner_lock.input.raw.events.push(egui::Event::Copy); + runner_lock.needs_repaint.repaint_asap(); + }, + )?; + + for event_name in &["load", "pagehide", "pageshow", "resize"] { + runner_container.add_event_listener( + &window, + event_name, + |_: web_sys::Event, runner_lock| { + runner_lock.needs_repaint.repaint_asap(); + }, + )?; + } + + runner_container.add_event_listener( + &window, + "hashchange", + |_: web_sys::Event, mut runner_lock| { + // `epi::Frame::info(&self)` clones `epi::IntegrationInfo`, but we need to modify the original here + runner_lock.frame.info.web_info.location.hash = location_hash(); + }, + )?; + + Ok(()) +} + +pub fn install_canvas_events(runner_container: &mut AppRunnerContainer) -> Result<(), JsValue> { + let canvas = canvas_element(runner_container.runner.lock().canvas_id()).unwrap(); + + let prevent_default_events = [ + // By default, right-clicks open a context menu. + // We don't want to do that (right clicks is handled by egui): + "contextmenu", + // Allow users to use ctrl-p for e.g. a command palette + "afterprint", + ]; + + for event_name in prevent_default_events { + let closure = + move |event: web_sys::MouseEvent, + mut _runner_lock: egui::mutex::MutexGuard<'_, AppRunner>| { + event.prevent_default(); + // event.stop_propagation(); + // tracing::debug!("Preventing event {:?}", event_name); + }; + + runner_container.add_event_listener(&canvas, event_name, closure)?; + } + + runner_container.add_event_listener( + &canvas, + "mousedown", + |event: web_sys::MouseEvent, mut runner_lock: egui::mutex::MutexGuard<'_, AppRunner>| { + if let Some(button) = button_from_mouse_event(&event) { + let pos = pos_from_mouse_event(runner_lock.canvas_id(), &event); + let modifiers = runner_lock.input.raw.modifiers; + runner_lock + .input + .raw + .events + .push(egui::Event::PointerButton { + pos, + button, + pressed: true, + modifiers, + }); + runner_lock.needs_repaint.repaint_asap(); + } + event.stop_propagation(); + // Note: prevent_default breaks VSCode tab focusing, hence why we don't call it here. + }, + )?; + + runner_container.add_event_listener( + &canvas, + "mousemove", + |event: web_sys::MouseEvent, mut runner_lock| { + let pos = pos_from_mouse_event(runner_lock.canvas_id(), &event); + runner_lock + .input + .raw + .events + .push(egui::Event::PointerMoved(pos)); + runner_lock.needs_repaint.repaint_asap(); + event.stop_propagation(); + event.prevent_default(); + }, + )?; + + runner_container.add_event_listener( + &canvas, + "mouseup", + |event: web_sys::MouseEvent, mut runner_lock| { + if let Some(button) = button_from_mouse_event(&event) { + let pos = pos_from_mouse_event(runner_lock.canvas_id(), &event); + let modifiers = runner_lock.input.raw.modifiers; + runner_lock + .input + .raw + .events + .push(egui::Event::PointerButton { + pos, + button, + pressed: false, + modifiers, + }); + runner_lock.needs_repaint.repaint_asap(); + + text_agent::update_text_agent(runner_lock); + } + event.stop_propagation(); + event.prevent_default(); + }, + )?; + + runner_container.add_event_listener( + &canvas, + "mouseleave", + |event: web_sys::MouseEvent, mut runner_lock| { + runner_lock.input.raw.events.push(egui::Event::PointerGone); + runner_lock.needs_repaint.repaint_asap(); + event.stop_propagation(); + event.prevent_default(); + }, + )?; + + runner_container.add_event_listener( + &canvas, + "touchstart", + |event: web_sys::TouchEvent, mut runner_lock| { + let mut latest_touch_pos_id = runner_lock.input.latest_touch_pos_id; + let pos = + pos_from_touch_event(runner_lock.canvas_id(), &event, &mut latest_touch_pos_id); + runner_lock.input.latest_touch_pos_id = latest_touch_pos_id; + runner_lock.input.latest_touch_pos = Some(pos); + let modifiers = runner_lock.input.raw.modifiers; + runner_lock + .input + .raw + .events + .push(egui::Event::PointerButton { + pos, + button: egui::PointerButton::Primary, + pressed: true, + modifiers, + }); + + push_touches(&mut runner_lock, egui::TouchPhase::Start, &event); + runner_lock.needs_repaint.repaint_asap(); + event.stop_propagation(); + event.prevent_default(); + }, + )?; + + runner_container.add_event_listener( + &canvas, + "touchmove", + |event: web_sys::TouchEvent, mut runner_lock| { + let mut latest_touch_pos_id = runner_lock.input.latest_touch_pos_id; + let pos = + pos_from_touch_event(runner_lock.canvas_id(), &event, &mut latest_touch_pos_id); + runner_lock.input.latest_touch_pos_id = latest_touch_pos_id; + runner_lock.input.latest_touch_pos = Some(pos); + runner_lock + .input + .raw + .events + .push(egui::Event::PointerMoved(pos)); + + push_touches(&mut runner_lock, egui::TouchPhase::Move, &event); + runner_lock.needs_repaint.repaint_asap(); + event.stop_propagation(); + event.prevent_default(); + }, + )?; + + runner_container.add_event_listener( + &canvas, + "touchend", + |event: web_sys::TouchEvent, mut runner_lock| { + if let Some(pos) = runner_lock.input.latest_touch_pos { + let modifiers = runner_lock.input.raw.modifiers; + // First release mouse to click: + runner_lock + .input + .raw + .events + .push(egui::Event::PointerButton { + pos, + button: egui::PointerButton::Primary, + pressed: false, + modifiers, + }); + // Then remove hover effect: + runner_lock.input.raw.events.push(egui::Event::PointerGone); + + push_touches(&mut runner_lock, egui::TouchPhase::End, &event); + runner_lock.needs_repaint.repaint_asap(); + event.stop_propagation(); + event.prevent_default(); + } + + // Finally, focus or blur text agent to toggle mobile keyboard: + text_agent::update_text_agent(runner_lock); + }, + )?; + + runner_container.add_event_listener( + &canvas, + "touchcancel", + |event: web_sys::TouchEvent, mut runner_lock| { + push_touches(&mut runner_lock, egui::TouchPhase::Cancel, &event); + event.stop_propagation(); + event.prevent_default(); + }, + )?; + + runner_container.add_event_listener( + &canvas, + "wheel", + |event: web_sys::WheelEvent, mut runner_lock| { + let scroll_multiplier = match event.delta_mode() { + web_sys::WheelEvent::DOM_DELTA_PAGE => { + canvas_size_in_points(runner_lock.canvas_id()).y + } + web_sys::WheelEvent::DOM_DELTA_LINE => { + #[allow(clippy::let_and_return)] + let points_per_scroll_line = 8.0; // Note that this is intentionally different from what we use in winit. + points_per_scroll_line + } + _ => 1.0, // DOM_DELTA_PIXEL + }; + + let mut delta = + -scroll_multiplier * egui::vec2(event.delta_x() as f32, event.delta_y() as f32); + + // Report a zoom event in case CTRL (on Windows or Linux) or CMD (on Mac) is pressed. + // This if-statement is equivalent to how `Modifiers.command` is determined in + // `modifiers_from_event()`, but we cannot directly use that fn for a [`WheelEvent`]. + if event.ctrl_key() || event.meta_key() { + let factor = (delta.y / 200.0).exp(); + runner_lock.input.raw.events.push(egui::Event::Zoom(factor)); + } else { + if event.shift_key() { + // Treat as horizontal scrolling. + // Note: one Mac we already get horizontal scroll events when shift is down. + delta = egui::vec2(delta.x + delta.y, 0.0); + } + + runner_lock + .input + .raw + .events + .push(egui::Event::Scroll(delta)); + } + + runner_lock.needs_repaint.repaint_asap(); + event.stop_propagation(); + event.prevent_default(); + }, + )?; + + runner_container.add_event_listener( + &canvas, + "dragover", + |event: web_sys::DragEvent, mut runner_lock| { + if let Some(data_transfer) = event.data_transfer() { + runner_lock.input.raw.hovered_files.clear(); + for i in 0..data_transfer.items().length() { + if let Some(item) = data_transfer.items().get(i) { + runner_lock.input.raw.hovered_files.push(egui::HoveredFile { + mime: item.type_(), + ..Default::default() + }); + } + } + runner_lock.needs_repaint.repaint_asap(); + event.stop_propagation(); + event.prevent_default(); + } + }, + )?; + + runner_container.add_event_listener( + &canvas, + "dragleave", + |event: web_sys::DragEvent, mut runner_lock| { + runner_lock.input.raw.hovered_files.clear(); + runner_lock.needs_repaint.repaint_asap(); + event.stop_propagation(); + event.prevent_default(); + }, + )?; + + runner_container.add_event_listener(&canvas, "drop", { + let runner_ref = runner_container.runner.clone(); + + move |event: web_sys::DragEvent, mut runner_lock| { + if let Some(data_transfer) = event.data_transfer() { + runner_lock.input.raw.hovered_files.clear(); + runner_lock.needs_repaint.repaint_asap(); + // Unlock the runner so it can be locked after a future await point + drop(runner_lock); + + if let Some(files) = data_transfer.files() { + for i in 0..files.length() { + if let Some(file) = files.get(i) { + let name = file.name(); + let last_modified = std::time::UNIX_EPOCH + + std::time::Duration::from_millis(file.last_modified() as u64); + + tracing::debug!("Loading {:?} ({} bytes)…", name, file.size()); + + let future = wasm_bindgen_futures::JsFuture::from(file.array_buffer()); + + let runner_ref = runner_ref.clone(); + let future = async move { + match future.await { + Ok(array_buffer) => { + let bytes = js_sys::Uint8Array::new(&array_buffer).to_vec(); + tracing::debug!( + "Loaded {:?} ({} bytes).", + name, + bytes.len() + ); + + // Re-lock the mutex on the other side of the await point + let mut runner_lock = runner_ref.lock(); + runner_lock.input.raw.dropped_files.push( + egui::DroppedFile { + name, + last_modified: Some(last_modified), + bytes: Some(bytes.into()), + ..Default::default() + }, + ); + runner_lock.needs_repaint.repaint_asap(); + } + Err(err) => { + tracing::error!("Failed to read file: {:?}", err); + } + } + }; + wasm_bindgen_futures::spawn_local(future); + } + } + } + event.stop_propagation(); + event.prevent_default(); + } + } + })?; + + Ok(()) +} diff --git a/nevmes-gui/crates/eframe/src/web/input.rs b/nevmes-gui/crates/eframe/src/web/input.rs new file mode 100644 index 0000000..443c26f --- /dev/null +++ b/nevmes-gui/crates/eframe/src/web/input.rs @@ -0,0 +1,217 @@ +use super::{canvas_element, canvas_origin, AppRunner}; + +pub fn pos_from_mouse_event(canvas_id: &str, event: &web_sys::MouseEvent) -> egui::Pos2 { + let canvas = canvas_element(canvas_id).unwrap(); + let rect = canvas.get_bounding_client_rect(); + egui::Pos2 { + x: event.client_x() as f32 - rect.left() as f32, + y: event.client_y() as f32 - rect.top() as f32, + } +} + +pub fn button_from_mouse_event(event: &web_sys::MouseEvent) -> Option { + match event.button() { + 0 => Some(egui::PointerButton::Primary), + 1 => Some(egui::PointerButton::Middle), + 2 => Some(egui::PointerButton::Secondary), + 3 => Some(egui::PointerButton::Extra1), + 4 => Some(egui::PointerButton::Extra2), + _ => None, + } +} + +/// A single touch is translated to a pointer movement. When a second touch is added, the pointer +/// should not jump to a different position. Therefore, we do not calculate the average position +/// of all touches, but we keep using the same touch as long as it is available. +/// +/// `touch_id_for_pos` is the [`TouchId`](egui::TouchId) of the [`Touch`](web_sys::Touch) we previously used to determine the +/// pointer position. +pub fn pos_from_touch_event( + canvas_id: &str, + event: &web_sys::TouchEvent, + touch_id_for_pos: &mut Option, +) -> egui::Pos2 { + let touch_for_pos = if let Some(touch_id_for_pos) = touch_id_for_pos { + // search for the touch we previously used for the position + // (unfortunately, `event.touches()` is not a rust collection): + (0..event.touches().length()) + .into_iter() + .map(|i| event.touches().get(i).unwrap()) + .find(|touch| egui::TouchId::from(touch.identifier()) == *touch_id_for_pos) + } else { + None + }; + // Use the touch found above or pick the first, or return a default position if there is no + // touch at all. (The latter is not expected as the current method is only called when there is + // at least one touch.) + touch_for_pos + .or_else(|| event.touches().get(0)) + .map_or(Default::default(), |touch| { + *touch_id_for_pos = Some(egui::TouchId::from(touch.identifier())); + pos_from_touch(canvas_origin(canvas_id), &touch) + }) +} + +fn pos_from_touch(canvas_origin: egui::Pos2, touch: &web_sys::Touch) -> egui::Pos2 { + egui::Pos2 { + x: touch.page_x() as f32 - canvas_origin.x, + y: touch.page_y() as f32 - canvas_origin.y, + } +} + +pub fn push_touches(runner: &mut AppRunner, phase: egui::TouchPhase, event: &web_sys::TouchEvent) { + let canvas_origin = canvas_origin(runner.canvas_id()); + for touch_idx in 0..event.changed_touches().length() { + if let Some(touch) = event.changed_touches().item(touch_idx) { + runner.input.raw.events.push(egui::Event::Touch { + device_id: egui::TouchDeviceId(0), + id: egui::TouchId::from(touch.identifier()), + phase, + pos: pos_from_touch(canvas_origin, &touch), + force: touch.force(), + }); + } + } +} + +/// Web sends all keys as strings, so it is up to us to figure out if it is +/// a real text input or the name of a key. +pub fn should_ignore_key(key: &str) -> bool { + let is_function_key = key.starts_with('F') && key.len() > 1; + is_function_key + || matches!( + key, + "Alt" + | "ArrowDown" + | "ArrowLeft" + | "ArrowRight" + | "ArrowUp" + | "Backspace" + | "CapsLock" + | "ContextMenu" + | "Control" + | "Delete" + | "End" + | "Enter" + | "Esc" + | "Escape" + | "GroupNext" // https://github.com/emilk/egui/issues/510 + | "Help" + | "Home" + | "Insert" + | "Meta" + | "NumLock" + | "PageDown" + | "PageUp" + | "Pause" + | "ScrollLock" + | "Shift" + | "Tab" + ) +} + +/// Web sends all all keys as strings, so it is up to us to figure out if it is +/// a real text input or the name of a key. +pub fn translate_key(key: &str) -> Option { + use egui::Key; + + match key { + "ArrowDown" => Some(Key::ArrowDown), + "ArrowLeft" => Some(Key::ArrowLeft), + "ArrowRight" => Some(Key::ArrowRight), + "ArrowUp" => Some(Key::ArrowUp), + + "Esc" | "Escape" => Some(Key::Escape), + "Tab" => Some(Key::Tab), + "Backspace" => Some(Key::Backspace), + "Enter" => Some(Key::Enter), + "Space" | " " => Some(Key::Space), + + "Help" | "Insert" => Some(Key::Insert), + "Delete" => Some(Key::Delete), + "Home" => Some(Key::Home), + "End" => Some(Key::End), + "PageUp" => Some(Key::PageUp), + "PageDown" => Some(Key::PageDown), + + "-" => Some(Key::Minus), + "+" | "=" => Some(Key::PlusEquals), + + "0" => Some(Key::Num0), + "1" => Some(Key::Num1), + "2" => Some(Key::Num2), + "3" => Some(Key::Num3), + "4" => Some(Key::Num4), + "5" => Some(Key::Num5), + "6" => Some(Key::Num6), + "7" => Some(Key::Num7), + "8" => Some(Key::Num8), + "9" => Some(Key::Num9), + + "a" | "A" => Some(Key::A), + "b" | "B" => Some(Key::B), + "c" | "C" => Some(Key::C), + "d" | "D" => Some(Key::D), + "e" | "E" => Some(Key::E), + "f" | "F" => Some(Key::F), + "g" | "G" => Some(Key::G), + "h" | "H" => Some(Key::H), + "i" | "I" => Some(Key::I), + "j" | "J" => Some(Key::J), + "k" | "K" => Some(Key::K), + "l" | "L" => Some(Key::L), + "m" | "M" => Some(Key::M), + "n" | "N" => Some(Key::N), + "o" | "O" => Some(Key::O), + "p" | "P" => Some(Key::P), + "q" | "Q" => Some(Key::Q), + "r" | "R" => Some(Key::R), + "s" | "S" => Some(Key::S), + "t" | "T" => Some(Key::T), + "u" | "U" => Some(Key::U), + "v" | "V" => Some(Key::V), + "w" | "W" => Some(Key::W), + "x" | "X" => Some(Key::X), + "y" | "Y" => Some(Key::Y), + "z" | "Z" => Some(Key::Z), + + "F1" => Some(Key::F1), + "F2" => Some(Key::F2), + "F3" => Some(Key::F3), + "F4" => Some(Key::F4), + "F5" => Some(Key::F5), + "F6" => Some(Key::F6), + "F7" => Some(Key::F7), + "F8" => Some(Key::F8), + "F9" => Some(Key::F9), + "F10" => Some(Key::F10), + "F11" => Some(Key::F11), + "F12" => Some(Key::F12), + "F13" => Some(Key::F13), + "F14" => Some(Key::F14), + "F15" => Some(Key::F15), + "F16" => Some(Key::F16), + "F17" => Some(Key::F17), + "F18" => Some(Key::F18), + "F19" => Some(Key::F19), + "F20" => Some(Key::F20), + + _ => None, + } +} + +pub fn modifiers_from_event(event: &web_sys::KeyboardEvent) -> egui::Modifiers { + egui::Modifiers { + alt: event.alt_key(), + ctrl: event.ctrl_key(), + shift: event.shift_key(), + + // Ideally we should know if we are running or mac or not, + // but this works good enough for now. + mac_cmd: event.meta_key(), + + // Ideally we should know if we are running or mac or not, + // but this works good enough for now. + command: event.ctrl_key() || event.meta_key(), + } +} diff --git a/nevmes-gui/crates/eframe/src/web/mod.rs b/nevmes-gui/crates/eframe/src/web/mod.rs new file mode 100644 index 0000000..dcf58b0 --- /dev/null +++ b/nevmes-gui/crates/eframe/src/web/mod.rs @@ -0,0 +1,258 @@ +//! [`egui`] bindings for web apps (compiling to WASM). + +#![allow(clippy::missing_errors_doc)] // So many `-> Result<_, JsValue>` + +pub mod backend; +mod events; +mod input; +pub mod screen_reader; +pub mod storage; +mod text_agent; + +#[cfg(not(any(feature = "glow", feature = "wgpu")))] +compile_error!("You must enable either the 'glow' or 'wgpu' feature"); + +mod web_painter; + +#[cfg(feature = "glow")] +mod web_painter_glow; +#[cfg(feature = "glow")] +pub(crate) type ActiveWebPainter = web_painter_glow::WebPainterGlow; + +#[cfg(feature = "wgpu")] +mod web_painter_wgpu; +#[cfg(all(feature = "wgpu", not(feature = "glow")))] +pub(crate) type ActiveWebPainter = web_painter_wgpu::WebPainterWgpu; + +pub use backend::*; +pub use events::*; +pub use storage::*; + +use std::collections::BTreeMap; +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, +}; + +use egui::Vec2; +use wasm_bindgen::prelude::*; +use web_sys::EventTarget; + +use input::*; + +use crate::Theme; + +// ---------------------------------------------------------------------------- + +/// Current time in seconds (since undefined point in time). +/// +/// Monotonically increasing. +pub fn now_sec() -> f64 { + web_sys::window() + .expect("should have a Window") + .performance() + .expect("should have a Performance") + .now() + / 1000.0 +} + +#[allow(dead_code)] +pub fn screen_size_in_native_points() -> Option { + let window = web_sys::window()?; + Some(egui::vec2( + window.inner_width().ok()?.as_f64()? as f32, + window.inner_height().ok()?.as_f64()? as f32, + )) +} + +pub fn native_pixels_per_point() -> f32 { + let pixels_per_point = web_sys::window().unwrap().device_pixel_ratio() as f32; + if pixels_per_point > 0.0 && pixels_per_point.is_finite() { + pixels_per_point + } else { + 1.0 + } +} + +pub fn system_theme() -> Option { + let dark_mode = web_sys::window()? + .match_media("(prefers-color-scheme: dark)") + .ok()?? + .matches(); + Some(if dark_mode { Theme::Dark } else { Theme::Light }) +} + +pub fn canvas_element(canvas_id: &str) -> Option { + let document = web_sys::window()?.document()?; + let canvas = document.get_element_by_id(canvas_id)?; + canvas.dyn_into::().ok() +} + +pub fn canvas_element_or_die(canvas_id: &str) -> web_sys::HtmlCanvasElement { + canvas_element(canvas_id) + .unwrap_or_else(|| panic!("Failed to find canvas with id {:?}", canvas_id)) +} + +fn canvas_origin(canvas_id: &str) -> egui::Pos2 { + let rect = canvas_element(canvas_id) + .unwrap() + .get_bounding_client_rect(); + egui::pos2(rect.left() as f32, rect.top() as f32) +} + +pub fn canvas_size_in_points(canvas_id: &str) -> egui::Vec2 { + let canvas = canvas_element(canvas_id).unwrap(); + let pixels_per_point = native_pixels_per_point(); + egui::vec2( + canvas.width() as f32 / pixels_per_point, + canvas.height() as f32 / pixels_per_point, + ) +} + +pub fn resize_canvas_to_screen_size(canvas_id: &str, max_size_points: egui::Vec2) -> Option<()> { + let canvas = canvas_element(canvas_id)?; + let parent = canvas.parent_element()?; + + let width = parent.scroll_width(); + let height = parent.scroll_height(); + + let canvas_real_size = Vec2 { + x: width as f32, + y: height as f32, + }; + + if width <= 0 || height <= 0 { + tracing::error!("egui canvas parent size is {}x{}. Try adding `html, body {{ height: 100%; width: 100% }}` to your CSS!", width, height); + } + + let pixels_per_point = native_pixels_per_point(); + + let max_size_pixels = pixels_per_point * max_size_points; + + let canvas_size_pixels = pixels_per_point * canvas_real_size; + let canvas_size_pixels = canvas_size_pixels.min(max_size_pixels); + let canvas_size_points = canvas_size_pixels / pixels_per_point; + + // Make sure that the height and width are always even numbers. + // otherwise, the page renders blurry on some platforms. + // See https://github.com/emilk/egui/issues/103 + fn round_to_even(v: f32) -> f32 { + (v / 2.0).round() * 2.0 + } + + canvas + .style() + .set_property( + "width", + &format!("{}px", round_to_even(canvas_size_points.x)), + ) + .ok()?; + canvas + .style() + .set_property( + "height", + &format!("{}px", round_to_even(canvas_size_points.y)), + ) + .ok()?; + canvas.set_width(round_to_even(canvas_size_pixels.x) as u32); + canvas.set_height(round_to_even(canvas_size_pixels.y) as u32); + + Some(()) +} + +// ---------------------------------------------------------------------------- + +pub fn set_cursor_icon(cursor: egui::CursorIcon) -> Option<()> { + let document = web_sys::window()?.document()?; + document + .body()? + .style() + .set_property("cursor", cursor_web_name(cursor)) + .ok() +} + +#[cfg(web_sys_unstable_apis)] +pub fn set_clipboard_text(s: &str) { + if let Some(window) = web_sys::window() { + if let Some(clipboard) = window.navigator().clipboard() { + let promise = clipboard.write_text(s); + let future = wasm_bindgen_futures::JsFuture::from(promise); + let future = async move { + if let Err(err) = future.await { + tracing::error!("Copy/cut action denied: {:?}", err); + } + }; + wasm_bindgen_futures::spawn_local(future); + } + } +} + +fn cursor_web_name(cursor: egui::CursorIcon) -> &'static str { + match cursor { + egui::CursorIcon::Alias => "alias", + egui::CursorIcon::AllScroll => "all-scroll", + egui::CursorIcon::Cell => "cell", + egui::CursorIcon::ContextMenu => "context-menu", + egui::CursorIcon::Copy => "copy", + egui::CursorIcon::Crosshair => "crosshair", + egui::CursorIcon::Default => "default", + egui::CursorIcon::Grab => "grab", + egui::CursorIcon::Grabbing => "grabbing", + egui::CursorIcon::Help => "help", + egui::CursorIcon::Move => "move", + egui::CursorIcon::NoDrop => "no-drop", + egui::CursorIcon::None => "none", + egui::CursorIcon::NotAllowed => "not-allowed", + egui::CursorIcon::PointingHand => "pointer", + egui::CursorIcon::Progress => "progress", + egui::CursorIcon::ResizeHorizontal => "ew-resize", + egui::CursorIcon::ResizeNeSw => "nesw-resize", + egui::CursorIcon::ResizeNwSe => "nwse-resize", + egui::CursorIcon::ResizeVertical => "ns-resize", + + egui::CursorIcon::ResizeEast => "e-resize", + egui::CursorIcon::ResizeSouthEast => "se-resize", + egui::CursorIcon::ResizeSouth => "s-resize", + egui::CursorIcon::ResizeSouthWest => "sw-resize", + egui::CursorIcon::ResizeWest => "w-resize", + egui::CursorIcon::ResizeNorthWest => "nw-resize", + egui::CursorIcon::ResizeNorth => "n-resize", + egui::CursorIcon::ResizeNorthEast => "ne-resize", + egui::CursorIcon::ResizeColumn => "col-resize", + egui::CursorIcon::ResizeRow => "row-resize", + + egui::CursorIcon::Text => "text", + egui::CursorIcon::VerticalText => "vertical-text", + egui::CursorIcon::Wait => "wait", + egui::CursorIcon::ZoomIn => "zoom-in", + egui::CursorIcon::ZoomOut => "zoom-out", + } +} + +pub fn open_url(url: &str, new_tab: bool) -> Option<()> { + let name = if new_tab { "_blank" } else { "_self" }; + + web_sys::window()? + .open_with_url_and_target(url, name) + .ok()?; + Some(()) +} + +/// e.g. "#fragment" part of "www.example.com/index.html#fragment", +/// +/// Percent decoded +pub fn location_hash() -> String { + percent_decode( + &web_sys::window() + .unwrap() + .location() + .hash() + .unwrap_or_default(), + ) +} + +pub fn percent_decode(s: &str) -> String { + percent_encoding::percent_decode_str(s) + .decode_utf8_lossy() + .to_string() +} diff --git a/nevmes-gui/crates/eframe/src/web/screen_reader.rs b/nevmes-gui/crates/eframe/src/web/screen_reader.rs new file mode 100644 index 0000000..058407b --- /dev/null +++ b/nevmes-gui/crates/eframe/src/web/screen_reader.rs @@ -0,0 +1,49 @@ +pub struct ScreenReader { + #[cfg(feature = "tts")] + tts: Option, +} + +#[cfg(not(feature = "tts"))] +#[allow(clippy::derivable_impls)] // False positive +impl Default for ScreenReader { + fn default() -> Self { + Self {} + } +} + +#[cfg(feature = "tts")] +impl Default for ScreenReader { + fn default() -> Self { + let tts = match tts::Tts::default() { + Ok(screen_reader) => { + tracing::debug!("Initialized screen reader."); + Some(screen_reader) + } + Err(err) => { + tracing::warn!("Failed to load screen reader: {}", err); + None + } + }; + Self { tts } + } +} + +impl ScreenReader { + #[cfg(not(feature = "tts"))] + #[allow(clippy::unused_self)] + pub fn speak(&mut self, _text: &str) {} + + #[cfg(feature = "tts")] + pub fn speak(&mut self, text: &str) { + if text.is_empty() { + return; + } + if let Some(tts) = &mut self.tts { + tracing::debug!("Speaking: {:?}", text); + let interrupt = true; + if let Err(err) = tts.speak(text, interrupt) { + tracing::warn!("Failed to read: {}", err); + } + } + } +} diff --git a/nevmes-gui/crates/eframe/src/web/storage.rs b/nevmes-gui/crates/eframe/src/web/storage.rs new file mode 100644 index 0000000..f0f3c84 --- /dev/null +++ b/nevmes-gui/crates/eframe/src/web/storage.rs @@ -0,0 +1,43 @@ +fn local_storage() -> Option { + web_sys::window()?.local_storage().ok()? +} + +pub fn local_storage_get(key: &str) -> Option { + local_storage().map(|storage| storage.get_item(key).ok())?? +} + +pub fn local_storage_set(key: &str, value: &str) { + local_storage().map(|storage| storage.set_item(key, value)); +} + +#[cfg(feature = "persistence")] +pub fn load_memory(ctx: &egui::Context) { + if let Some(memory_string) = local_storage_get("egui_memory_ron") { + match ron::from_str(&memory_string) { + Ok(memory) => { + ctx.memory_mut(|m| *m = memory); + } + Err(err) => { + tracing::error!("Failed to parse memory RON: {}", err); + } + } + } +} + +#[cfg(not(feature = "persistence"))] +pub fn load_memory(_: &egui::Context) {} + +#[cfg(feature = "persistence")] +pub fn save_memory(ctx: &egui::Context) { + match ctx.memory(|mem| ron::to_string(mem)) { + Ok(ron) => { + local_storage_set("egui_memory_ron", &ron); + } + Err(err) => { + tracing::error!("Failed to serialize memory as RON: {}", err); + } + } +} + +#[cfg(not(feature = "persistence"))] +pub fn save_memory(_: &egui::Context) {} diff --git a/nevmes-gui/crates/eframe/src/web/text_agent.rs b/nevmes-gui/crates/eframe/src/web/text_agent.rs new file mode 100644 index 0000000..de83dda --- /dev/null +++ b/nevmes-gui/crates/eframe/src/web/text_agent.rs @@ -0,0 +1,225 @@ +//! The text agent is an `` element used to trigger +//! mobile keyboard and IME input. + +use super::{canvas_element, AppRunner, AppRunnerContainer}; +use egui::mutex::MutexGuard; +use std::cell::Cell; +use std::rc::Rc; +use wasm_bindgen::prelude::*; + +static AGENT_ID: &str = "egui_text_agent"; + +pub fn text_agent() -> web_sys::HtmlInputElement { + web_sys::window() + .unwrap() + .document() + .unwrap() + .get_element_by_id(AGENT_ID) + .unwrap() + .dyn_into() + .unwrap() +} + +/// Text event handler, +pub fn install_text_agent(runner_container: &mut AppRunnerContainer) -> Result<(), JsValue> { + let window = web_sys::window().unwrap(); + let document = window.document().unwrap(); + let body = document.body().expect("document should have a body"); + let input = document + .create_element("input")? + .dyn_into::()?; + let input = std::rc::Rc::new(input); + input.set_id(AGENT_ID); + let is_composing = Rc::new(Cell::new(false)); + { + let style = input.style(); + // Transparent + style.set_property("opacity", "0").unwrap(); + // Hide under canvas + style.set_property("z-index", "-1").unwrap(); + } + // Set size as small as possible, in case user may click on it. + input.set_size(1); + input.set_autofocus(true); + input.set_hidden(true); + + // When IME is off + runner_container.add_event_listener(&input, "input", { + let input_clone = input.clone(); + let is_composing = is_composing.clone(); + + move |_event: web_sys::InputEvent, mut runner_lock| { + let text = input_clone.value(); + if !text.is_empty() && !is_composing.get() { + input_clone.set_value(""); + runner_lock.input.raw.events.push(egui::Event::Text(text)); + runner_lock.needs_repaint.repaint_asap(); + } + } + })?; + + { + // When IME is on, handle composition event + runner_container.add_event_listener(&input, "compositionstart", { + let input_clone = input.clone(); + let is_composing = is_composing.clone(); + + move |_event: web_sys::CompositionEvent, mut runner_lock: MutexGuard<'_, AppRunner>| { + is_composing.set(true); + input_clone.set_value(""); + + runner_lock + .input + .raw + .events + .push(egui::Event::CompositionStart); + runner_lock.needs_repaint.repaint_asap(); + } + })?; + + runner_container.add_event_listener( + &input, + "compositionupdate", + move |event: web_sys::CompositionEvent, mut runner_lock: MutexGuard<'_, AppRunner>| { + if let Some(event) = event.data().map(egui::Event::CompositionUpdate) { + runner_lock.input.raw.events.push(event); + runner_lock.needs_repaint.repaint_asap(); + } + }, + )?; + + runner_container.add_event_listener(&input, "compositionend", { + let input_clone = input.clone(); + + move |event: web_sys::CompositionEvent, mut runner_lock: MutexGuard<'_, AppRunner>| { + is_composing.set(false); + input_clone.set_value(""); + + if let Some(event) = event.data().map(egui::Event::CompositionEnd) { + runner_lock.input.raw.events.push(event); + runner_lock.needs_repaint.repaint_asap(); + } + } + })?; + } + + // When input lost focus, focus on it again. + // It is useful when user click somewhere outside canvas. + runner_container.add_event_listener( + &input, + "focusout", + move |_event: web_sys::MouseEvent, _| { + // Delay 10 ms, and focus again. + let func = js_sys::Function::new_no_args(&format!( + "document.getElementById('{}').focus()", + AGENT_ID + )); + window + .set_timeout_with_callback_and_timeout_and_arguments_0(&func, 10) + .unwrap(); + }, + )?; + + body.append_child(&input)?; + + Ok(()) +} + +/// Focus or blur text agent to toggle mobile keyboard. +pub fn update_text_agent(runner: MutexGuard<'_, AppRunner>) -> Option<()> { + use web_sys::HtmlInputElement; + let window = web_sys::window()?; + let document = window.document()?; + let input: HtmlInputElement = document.get_element_by_id(AGENT_ID)?.dyn_into().unwrap(); + let canvas_style = canvas_element(runner.canvas_id())?.style(); + + if runner.mutable_text_under_cursor { + let is_already_editing = input.hidden(); + if is_already_editing { + input.set_hidden(false); + input.focus().ok()?; + + // Move up canvas so that text edit is shown at ~30% of screen height. + // Only on touch screens, when keyboard popups. + if let Some(latest_touch_pos) = runner.input.latest_touch_pos { + let window_height = window.inner_height().ok()?.as_f64()? as f32; + let current_rel = latest_touch_pos.y / window_height; + + // estimated amount of screen covered by keyboard + let keyboard_fraction = 0.5; + + if current_rel > keyboard_fraction { + // below the keyboard + + let target_rel = 0.3; + + // Note: `delta` is negative, since we are moving the canvas UP + let delta = target_rel - current_rel; + + let delta = delta.max(-keyboard_fraction); // Don't move it crazy much + + let new_pos_percent = format!("{}%", (delta * 100.0).round()); + + canvas_style.set_property("position", "absolute").ok()?; + canvas_style.set_property("top", &new_pos_percent).ok()?; + } + } + } + } else { + // Drop runner lock + drop(runner); + + // Holding the runner lock while calling input.blur() causes a panic. + // This is most probably caused by the browser running the event handler + // for the triggered blur event synchronously, meaning that the mutex + // lock does not get dropped by the time another event handler is called. + // + // Why this didn't exist before #1290 is a mystery to me, but it exists now + // and this apparently is the fix for it + // + // ¯\_(ツ)_/¯ - @DusterTheFirst + input.blur().ok()?; + + input.set_hidden(true); + canvas_style.set_property("position", "absolute").ok()?; + canvas_style.set_property("top", "0%").ok()?; // move back to normal position + } + Some(()) +} + +/// If context is running under mobile device? +fn is_mobile() -> Option { + const MOBILE_DEVICE: [&str; 6] = ["Android", "iPhone", "iPad", "iPod", "webOS", "BlackBerry"]; + + let user_agent = web_sys::window()?.navigator().user_agent().ok()?; + let is_mobile = MOBILE_DEVICE.iter().any(|&name| user_agent.contains(name)); + Some(is_mobile) +} + +// Move text agent to text cursor's position, on desktop/laptop, +// candidate window moves following text element (agent), +// so it appears that the IME candidate window moves with text cursor. +// On mobile devices, there is no need to do that. +pub fn move_text_cursor(cursor: Option, canvas_id: &str) -> Option<()> { + let style = text_agent().style(); + // Note: movint agent on mobile devices will lead to unpredictable scroll. + if is_mobile() == Some(false) { + cursor.as_ref().and_then(|&egui::Pos2 { x, y }| { + let canvas = canvas_element(canvas_id)?; + let bounding_rect = text_agent().get_bounding_client_rect(); + let y = (y + (canvas.scroll_top() + canvas.offset_top()) as f32) + .min(canvas.client_height() as f32 - bounding_rect.height() as f32); + let x = x + (canvas.scroll_left() + canvas.offset_left()) as f32; + // Canvas is translated 50% horizontally in html. + let x = (x - canvas.offset_width() as f32 / 2.0) + .min(canvas.client_width() as f32 - bounding_rect.width() as f32); + style.set_property("position", "absolute").ok()?; + style.set_property("top", &format!("{}px", y)).ok()?; + style.set_property("left", &format!("{}px", x)).ok() + }) + } else { + style.set_property("position", "absolute").ok()?; + style.set_property("top", "0px").ok()?; + style.set_property("left", "0px").ok() + } +} diff --git a/nevmes-gui/crates/eframe/src/web/web_painter.rs b/nevmes-gui/crates/eframe/src/web/web_painter.rs new file mode 100644 index 0000000..9c7631b --- /dev/null +++ b/nevmes-gui/crates/eframe/src/web/web_painter.rs @@ -0,0 +1,29 @@ +use wasm_bindgen::JsValue; + +/// Renderer for a browser canvas. +/// As of writing we're not allowing to decide on the painter at runtime, +/// therefore this trait is merely there for specifying and documenting the interface. +pub(crate) trait WebPainter { + // Create a new web painter targeting a given canvas. + // fn new(canvas_id: &str, options: &WebOptions) -> Result + // where + // Self: Sized; + + /// Id of the canvas in use. + fn canvas_id(&self) -> &str; + + /// Maximum size of a texture in one direction. + fn max_texture_side(&self) -> usize; + + /// Update all internal textures and paint gui. + fn paint_and_update_textures( + &mut self, + clear_color: [f32; 4], + clipped_primitives: &[egui::ClippedPrimitive], + pixels_per_point: f32, + textures_delta: &egui::TexturesDelta, + ) -> Result<(), JsValue>; + + /// Destroy all resources. + fn destroy(&mut self); +} diff --git a/nevmes-gui/crates/eframe/src/web/web_painter_glow.rs b/nevmes-gui/crates/eframe/src/web/web_painter_glow.rs new file mode 100644 index 0000000..0fe7e12 --- /dev/null +++ b/nevmes-gui/crates/eframe/src/web/web_painter_glow.rs @@ -0,0 +1,184 @@ +use wasm_bindgen::JsCast; +use wasm_bindgen::JsValue; +use web_sys::HtmlCanvasElement; + +use egui_glow::glow; + +use crate::{WebGlContextOption, WebOptions}; + +use super::web_painter::WebPainter; + +pub(crate) struct WebPainterGlow { + canvas: HtmlCanvasElement, + canvas_id: String, + painter: egui_glow::Painter, +} + +impl WebPainterGlow { + pub fn gl(&self) -> &std::sync::Arc { + self.painter.gl() + } + + pub async fn new(canvas_id: &str, options: &WebOptions) -> Result { + let canvas = super::canvas_element_or_die(canvas_id); + + let (gl, shader_prefix) = + init_glow_context_from_canvas(&canvas, options.webgl_context_option)?; + let gl = std::sync::Arc::new(gl); + + let painter = egui_glow::Painter::new(gl, shader_prefix, None) + .map_err(|error| format!("Error starting glow painter: {}", error))?; + + Ok(Self { + canvas, + canvas_id: canvas_id.to_owned(), + painter, + }) + } +} + +impl WebPainter for WebPainterGlow { + fn max_texture_side(&self) -> usize { + self.painter.max_texture_side() + } + + fn canvas_id(&self) -> &str { + &self.canvas_id + } + + fn paint_and_update_textures( + &mut self, + clear_color: [f32; 4], + clipped_primitives: &[egui::ClippedPrimitive], + pixels_per_point: f32, + textures_delta: &egui::TexturesDelta, + ) -> Result<(), JsValue> { + let canvas_dimension = [self.canvas.width(), self.canvas.height()]; + + for (id, image_delta) in &textures_delta.set { + self.painter.set_texture(*id, image_delta); + } + + egui_glow::painter::clear(self.painter.gl(), canvas_dimension, clear_color); + self.painter + .paint_primitives(canvas_dimension, pixels_per_point, clipped_primitives); + + for &id in &textures_delta.free { + self.painter.free_texture(id); + } + + Ok(()) + } + + fn destroy(&mut self) { + self.painter.destroy(); + } +} + +/// Returns glow context and shader prefix. +fn init_glow_context_from_canvas( + canvas: &HtmlCanvasElement, + options: WebGlContextOption, +) -> Result<(glow::Context, &'static str), String> { + let result = match options { + // Force use WebGl1 + WebGlContextOption::WebGl1 => init_webgl1(canvas), + // Force use WebGl2 + WebGlContextOption::WebGl2 => init_webgl2(canvas), + // Trying WebGl2 first + WebGlContextOption::BestFirst => init_webgl2(canvas).or_else(|| init_webgl1(canvas)), + // Trying WebGl1 first (useful for testing). + WebGlContextOption::CompatibilityFirst => { + init_webgl1(canvas).or_else(|| init_webgl2(canvas)) + } + }; + + if let Some(result) = result { + Ok(result) + } else { + Err("WebGL isn't supported".into()) + } +} + +fn init_webgl1(canvas: &HtmlCanvasElement) -> Option<(glow::Context, &'static str)> { + let gl1_ctx = canvas + .get_context("webgl") + .expect("Failed to query about WebGL2 context"); + + let gl1_ctx = gl1_ctx?; + tracing::debug!("WebGL1 selected."); + + let gl1_ctx = gl1_ctx + .dyn_into::() + .unwrap(); + + let shader_prefix = if webgl1_requires_brightening(&gl1_ctx) { + tracing::debug!("Enabling webkitGTK brightening workaround."); + "#define APPLY_BRIGHTENING_GAMMA" + } else { + "" + }; + + let gl = glow::Context::from_webgl1_context(gl1_ctx); + + Some((gl, shader_prefix)) +} + +fn init_webgl2(canvas: &HtmlCanvasElement) -> Option<(glow::Context, &'static str)> { + let gl2_ctx = canvas + .get_context("webgl2") + .expect("Failed to query about WebGL2 context"); + + let gl2_ctx = gl2_ctx?; + tracing::debug!("WebGL2 selected."); + + let gl2_ctx = gl2_ctx + .dyn_into::() + .unwrap(); + let gl = glow::Context::from_webgl2_context(gl2_ctx); + let shader_prefix = ""; + + Some((gl, shader_prefix)) +} + +fn webgl1_requires_brightening(gl: &web_sys::WebGlRenderingContext) -> bool { + // See https://github.com/emilk/egui/issues/794 + + // detect WebKitGTK + + // WebKitGTK use WebKit default unmasked vendor and renderer + // but safari use same vendor and renderer + // so exclude "Mac OS X" user-agent. + let user_agent = web_sys::window().unwrap().navigator().user_agent().unwrap(); + !user_agent.contains("Mac OS X") && is_safari_and_webkit_gtk(gl) +} + +/// detecting Safari and `webkitGTK`. +/// +/// Safari and `webkitGTK` use unmasked renderer :Apple GPU +/// +/// If we detect safari or `webkitGTKs` returns true. +/// +/// This function used to avoid displaying linear color with `sRGB` supported systems. +fn is_safari_and_webkit_gtk(gl: &web_sys::WebGlRenderingContext) -> bool { + // This call produces a warning in Firefox ("WEBGL_debug_renderer_info is deprecated in Firefox and will be removed.") + // but unless we call it we get errors in Chrome when we call `get_parameter` below. + // TODO(emilk): do something smart based on user agent? + if gl + .get_extension("WEBGL_debug_renderer_info") + .unwrap() + .is_some() + { + if let Ok(renderer) = + gl.get_parameter(web_sys::WebglDebugRendererInfo::UNMASKED_RENDERER_WEBGL) + { + if let Some(renderer) = renderer.as_string() { + if renderer.contains("Apple") { + return true; + } + } + } + } + + false +} diff --git a/nevmes-gui/crates/eframe/src/web/web_painter_wgpu.rs b/nevmes-gui/crates/eframe/src/web/web_painter_wgpu.rs new file mode 100644 index 0000000..ab55373 --- /dev/null +++ b/nevmes-gui/crates/eframe/src/web/web_painter_wgpu.rs @@ -0,0 +1,282 @@ +use std::sync::Arc; + +use wasm_bindgen::JsValue; +use web_sys::HtmlCanvasElement; + +use egui::mutex::RwLock; +use egui_wgpu::{renderer::ScreenDescriptor, RenderState, SurfaceErrorAction}; + +use crate::WebOptions; + +use super::web_painter::WebPainter; + +pub(crate) struct WebPainterWgpu { + canvas: HtmlCanvasElement, + canvas_id: String, + surface: wgpu::Surface, + surface_configuration: wgpu::SurfaceConfiguration, + limits: wgpu::Limits, + render_state: Option, + on_surface_error: Arc SurfaceErrorAction>, + depth_format: Option, + depth_texture_view: Option, +} + +impl WebPainterWgpu { + #[allow(unused)] // only used if `wgpu` is the only active feature. + pub fn render_state(&self) -> Option { + self.render_state.clone() + } + + pub fn generate_depth_texture_view( + &self, + render_state: &RenderState, + width_in_pixels: u32, + height_in_pixels: u32, + ) -> Option { + let device = &render_state.device; + self.depth_format.map(|depth_format| { + device + .create_texture(&wgpu::TextureDescriptor { + label: Some("egui_depth_texture"), + size: wgpu::Extent3d { + width: width_in_pixels, + height: height_in_pixels, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: depth_format, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + view_formats: &[depth_format], + }) + .create_view(&wgpu::TextureViewDescriptor::default()) + }) + } + + #[allow(unused)] // only used if `wgpu` is the only active feature. + pub async fn new(canvas_id: &str, options: &WebOptions) -> Result { + tracing::debug!("Creating wgpu painter"); + + let canvas = super::canvas_element_or_die(canvas_id); + + let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { + backends: options.wgpu_options.backends, + dx12_shader_compiler: Default::default(), + }); + let surface = instance + .create_surface_from_canvas(&canvas) + .map_err(|err| format!("failed to create wgpu surface: {err}"))?; + + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: options.wgpu_options.power_preference, + force_fallback_adapter: false, + compatible_surface: None, + }) + .await + .ok_or_else(|| "No suitable GPU adapters found on the system".to_owned())?; + + let (device, queue) = adapter + .request_device( + &options.wgpu_options.device_descriptor, + None, // Capture doesn't work in the browser environment. + ) + .await + .map_err(|err| format!("Failed to find wgpu device: {}", err))?; + + let target_format = + egui_wgpu::preferred_framebuffer_format(&surface.get_capabilities(&adapter).formats); + + let depth_format = options.wgpu_options.depth_format; + let renderer = egui_wgpu::Renderer::new(&device, target_format, depth_format, 1); + let render_state = RenderState { + device: Arc::new(device), + queue: Arc::new(queue), + target_format, + renderer: Arc::new(RwLock::new(renderer)), + }; + + let surface_configuration = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: target_format, + width: 0, + height: 0, + present_mode: options.wgpu_options.present_mode, + alpha_mode: wgpu::CompositeAlphaMode::Auto, + view_formats: vec![target_format], + }; + + tracing::debug!("wgpu painter initialized."); + + Ok(Self { + canvas, + canvas_id: canvas_id.to_owned(), + render_state: Some(render_state), + surface, + surface_configuration, + depth_format, + depth_texture_view: None, + limits: options.wgpu_options.device_descriptor.limits.clone(), + on_surface_error: options.wgpu_options.on_surface_error.clone(), + }) + } +} + +impl WebPainter for WebPainterWgpu { + fn canvas_id(&self) -> &str { + &self.canvas_id + } + + fn max_texture_side(&self) -> usize { + self.limits.max_texture_dimension_2d as _ + } + + fn paint_and_update_textures( + &mut self, + clear_color: [f32; 4], + clipped_primitives: &[egui::ClippedPrimitive], + pixels_per_point: f32, + textures_delta: &egui::TexturesDelta, + ) -> Result<(), JsValue> { + let size_in_pixels = [self.canvas.width(), self.canvas.height()]; + + let render_state = if let Some(render_state) = &self.render_state { + render_state + } else { + return Err(JsValue::from_str( + "Can't paint, wgpu renderer was already disposed", + )); + }; + + let mut encoder = + render_state + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("egui_webpainter_paint_and_update_textures"), + }); + + // Upload all resources for the GPU. + let screen_descriptor = ScreenDescriptor { + size_in_pixels, + pixels_per_point, + }; + + let user_cmd_bufs = { + let mut renderer = render_state.renderer.write(); + for (id, image_delta) in &textures_delta.set { + renderer.update_texture( + &render_state.device, + &render_state.queue, + *id, + image_delta, + ); + } + + renderer.update_buffers( + &render_state.device, + &render_state.queue, + &mut encoder, + clipped_primitives, + &screen_descriptor, + ) + }; + + // Resize surface if needed + let is_zero_sized_surface = size_in_pixels[0] == 0 || size_in_pixels[1] == 0; + let frame = if is_zero_sized_surface { + None + } else { + if size_in_pixels[0] != self.surface_configuration.width + || size_in_pixels[1] != self.surface_configuration.height + { + self.surface_configuration.width = size_in_pixels[0]; + self.surface_configuration.height = size_in_pixels[1]; + self.surface + .configure(&render_state.device, &self.surface_configuration); + self.depth_texture_view = self.generate_depth_texture_view( + render_state, + size_in_pixels[0], + size_in_pixels[1], + ); + } + + let frame = match self.surface.get_current_texture() { + Ok(frame) => frame, + #[allow(clippy::single_match_else)] + Err(e) => match (*self.on_surface_error)(e) { + SurfaceErrorAction::RecreateSurface => { + self.surface + .configure(&render_state.device, &self.surface_configuration); + return Ok(()); + } + SurfaceErrorAction::SkipFrame => { + return Ok(()); + } + }, + }; + + { + let renderer = render_state.renderer.read(); + let frame_view = frame + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &frame_view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color { + r: clear_color[0] as f64, + g: clear_color[1] as f64, + b: clear_color[2] as f64, + a: clear_color[3] as f64, + }), + store: true, + }, + })], + depth_stencil_attachment: self.depth_texture_view.as_ref().map(|view| { + wgpu::RenderPassDepthStencilAttachment { + view, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Clear(1.0), + store: false, + }), + stencil_ops: None, + } + }), + label: Some("egui_render"), + }); + + renderer.render(&mut render_pass, clipped_primitives, &screen_descriptor); + } + + Some(frame) + }; + + { + let mut renderer = render_state.renderer.write(); + for id in &textures_delta.free { + renderer.free_texture(id); + } + } + + // Submit the commands: both the main buffer and user-defined ones. + render_state.queue.submit( + user_cmd_bufs + .into_iter() + .chain(std::iter::once(encoder.finish())), + ); + + if let Some(frame) = frame { + frame.present(); + } + + Ok(()) + } + + fn destroy(&mut self) { + self.render_state = None; + } +} diff --git a/nevmes-gui/crates/egui-wgpu/CHANGELOG.md b/nevmes-gui/crates/egui-wgpu/CHANGELOG.md new file mode 100644 index 0000000..36cf7fe --- /dev/null +++ b/nevmes-gui/crates/egui-wgpu/CHANGELOG.md @@ -0,0 +1,37 @@ +# Changelog for egui-wgpu +All notable changes to the `egui-wgpu` integration will be noted in this file. + + +## Unreleased + + +## 0.21.0 - 2023-02-08 +* Update to `wgpu` 0.15 ([#2629](https://github.com/emilk/egui/pull/2629)) +* Return `Err` instead of panic if we can't find a device ([#2428](https://github.com/emilk/egui/pull/2428)). +* `winit::Painter::set_window` is now `async` ([#2434](https://github.com/emilk/egui/pull/2434)). +* `egui-wgpu` now only depends on `epaint` instead of the entire `egui` ([#2438](https://github.com/emilk/egui/pull/2438)). +* `winit::Painter` now supports transparent backbuffer ([#2684](https://github.com/emilk/egui/pull/2684)). + + +## 0.20.0 - 2022-12-08 - web support +* Renamed `RenderPass` to `Renderer`. +* Renamed `RenderPass::execute` to `RenderPass::render`. +* Renamed `RenderPass::execute_with_renderpass` to `Renderer::render` (replacing existing `Renderer::render`) +* Reexported `Renderer`. +* You can now use `egui-wgpu` on web, using WebGL ([#2107](https://github.com/emilk/egui/pull/2107)). +* `Renderer` no longer handles pass creation and depth buffer creation ([#2136](https://github.com/emilk/egui/pull/2136)) +* `PrepareCallback` now passes `wgpu::CommandEncoder` ([#2136](https://github.com/emilk/egui/pull/2136)) +* `PrepareCallback` can now returns `wgpu::CommandBuffer` that are bundled into a single `wgpu::Queue::submit` call ([#2230](https://github.com/emilk/egui/pull/2230)) +* Only a single vertex & index buffer is now created and resized when necessary (previously, vertex/index buffers were allocated for every mesh) ([#2148](https://github.com/emilk/egui/pull/2148)). +* `Renderer::update_texture` no longer creates a new `wgpu::Sampler` with every new texture ([#2198](https://github.com/emilk/egui/pull/2198)) +* `Painter`'s instance/device/adapter/surface creation is now configurable via `WgpuConfiguration` ([#2207](https://github.com/emilk/egui/pull/2207)) +* Fix panic on using a depth buffer ([#2316](https://github.com/emilk/egui/pull/2316)) + + +## 0.19.0 - 2022-08-20 +* Enables deferred render + surface state initialization for Android ([#1634](https://github.com/emilk/egui/pull/1634)). +* Make `RenderPass` `Send` and `Sync` ([#1883](https://github.com/emilk/egui/pull/1883)). + + +## 0.18.0 - 2022-05-15 +First published version since moving the code into the `egui` repository from . diff --git a/nevmes-gui/crates/egui-wgpu/Cargo.toml b/nevmes-gui/crates/egui-wgpu/Cargo.toml new file mode 100644 index 0000000..edc5813 --- /dev/null +++ b/nevmes-gui/crates/egui-wgpu/Cargo.toml @@ -0,0 +1,56 @@ +[package] +name = "egui-wgpu" +version = "0.21.0" +description = "Bindings for using egui natively using the wgpu library" +authors = [ + "Nils Hasenbanck ", + "embotech ", + "Emil Ernerfeldt ", +] +edition = "2021" +rust-version = "1.65" +homepage = "https://github.com/emilk/egui/tree/master/crates/egui-wgpu" +license = "MIT OR Apache-2.0" +readme = "README.md" +repository = "https://github.com/emilk/egui/tree/master/crates/egui-wgpu" +categories = ["gui", "game-development"] +keywords = ["wgpu", "egui", "gui", "gamedev"] +include = [ + "../LICENSE-APACHE", + "../LICENSE-MIT", + "**/*.rs", + "**/*.wgsl", + "Cargo.toml", +] + +[package.metadata.docs.rs] +all-features = true + + +[features] +## Enable profiling with the [`puffin`](https://docs.rs/puffin) crate. +puffin = ["dep:puffin"] + +## Enable [`winit`](https://docs.rs/winit) integration. +winit = ["dep:winit"] + + +[dependencies] +epaint = { version = "0.21.0", path = "../epaint", default-features = false, features = [ + "bytemuck", +] } + +bytemuck = "1.7" +tracing = { version = "0.1", default-features = false, features = ["std"] } +type-map = "0.5.0" +wgpu = "0.15.0" + +#! ### Optional dependencies +## Enable this when generating docs. +document-features = { version = "0.2", optional = true } + +winit = { version = "0.28", optional = true } + +# Native: +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +puffin = { version = "0.14", optional = true } diff --git a/nevmes-gui/crates/egui-wgpu/README.md b/nevmes-gui/crates/egui-wgpu/README.md new file mode 100644 index 0000000..9aab315 --- /dev/null +++ b/nevmes-gui/crates/egui-wgpu/README.md @@ -0,0 +1,10 @@ +# egui-wgpu + +[![Latest version](https://img.shields.io/crates/v/egui-wgpu.svg)](https://crates.io/crates/egui-wgpu) +[![Documentation](https://docs.rs/egui-wgpu/badge.svg)](https://docs.rs/egui-wgpu) +![MIT](https://img.shields.io/badge/license-MIT-blue.svg) +![Apache](https://img.shields.io/badge/license-Apache-blue.svg) + +This crates provides bindings between [`egui`](https://github.com/emilk/egui) and [wgpu](https://crates.io/crates/wgpu). + +This was originally hosted at https://github.com/hasenbanck/egui_wgpu_backend diff --git a/nevmes-gui/crates/egui-wgpu/src/egui.wgsl b/nevmes-gui/crates/egui-wgpu/src/egui.wgsl new file mode 100644 index 0000000..552bcbb --- /dev/null +++ b/nevmes-gui/crates/egui-wgpu/src/egui.wgsl @@ -0,0 +1,91 @@ +// Vertex shader bindings + +struct VertexOutput { + @location(0) tex_coord: vec2, + @location(1) color: vec4, // gamma 0-1 + @builtin(position) position: vec4, +}; + +struct Locals { + screen_size: vec2, + // Uniform buffers need to be at least 16 bytes in WebGL. + // See https://github.com/gfx-rs/wgpu/issues/2072 + _padding: vec2, +}; +@group(0) @binding(0) var r_locals: Locals; + +// 0-1 linear from 0-1 sRGB gamma +fn linear_from_gamma_rgb(srgb: vec3) -> vec3 { + let cutoff = srgb < vec3(0.04045); + let lower = srgb / vec3(12.92); + let higher = pow((srgb + vec3(0.055)) / vec3(1.055), vec3(2.4)); + return select(higher, lower, cutoff); +} + +// 0-1 sRGB gamma from 0-1 linear +fn gamma_from_linear_rgb(rgb: vec3) -> vec3 { + let cutoff = rgb < vec3(0.0031308); + let lower = rgb * vec3(12.92); + let higher = vec3(1.055) * pow(rgb, vec3(1.0 / 2.4)) - vec3(0.055); + return select(higher, lower, cutoff); +} + +// 0-1 sRGBA gamma from 0-1 linear +fn gamma_from_linear_rgba(linear_rgba: vec4) -> vec4 { + return vec4(gamma_from_linear_rgb(linear_rgba.rgb), linear_rgba.a); +} + +// [u8; 4] SRGB as u32 -> [r, g, b, a] in 0.-1 +fn unpack_color(color: u32) -> vec4 { + return vec4( + f32(color & 255u), + f32((color >> 8u) & 255u), + f32((color >> 16u) & 255u), + f32((color >> 24u) & 255u), + ) / 255.0; +} + +fn position_from_screen(screen_pos: vec2) -> vec4 { + return vec4( + 2.0 * screen_pos.x / r_locals.screen_size.x - 1.0, + 1.0 - 2.0 * screen_pos.y / r_locals.screen_size.y, + 0.0, + 1.0, + ); +} + +@vertex +fn vs_main( + @location(0) a_pos: vec2, + @location(1) a_tex_coord: vec2, + @location(2) a_color: u32, +) -> VertexOutput { + var out: VertexOutput; + out.tex_coord = a_tex_coord; + out.color = unpack_color(a_color); + out.position = position_from_screen(a_pos); + return out; +} + +// Fragment shader bindings + +@group(1) @binding(0) var r_tex_color: texture_2d; +@group(1) @binding(1) var r_tex_sampler: sampler; + +@fragment +fn fs_main_linear_framebuffer(in: VertexOutput) -> @location(0) vec4 { + // We always have an sRGB aware texture at the moment. + let tex_linear = textureSample(r_tex_color, r_tex_sampler, in.tex_coord); + let tex_gamma = gamma_from_linear_rgba(tex_linear); + let out_color_gamma = in.color * tex_gamma; + return vec4(linear_from_gamma_rgb(out_color_gamma.rgb), out_color_gamma.a); +} + +@fragment +fn fs_main_gamma_framebuffer(in: VertexOutput) -> @location(0) vec4 { + // We always have an sRGB aware texture at the moment. + let tex_linear = textureSample(r_tex_color, r_tex_sampler, in.tex_coord); + let tex_gamma = gamma_from_linear_rgba(tex_linear); + let out_color_gamma = in.color * tex_gamma; + return out_color_gamma; +} diff --git a/nevmes-gui/crates/egui-wgpu/src/lib.rs b/nevmes-gui/crates/egui-wgpu/src/lib.rs new file mode 100644 index 0000000..e5ba7f5 --- /dev/null +++ b/nevmes-gui/crates/egui-wgpu/src/lib.rs @@ -0,0 +1,155 @@ +//! This crates provides bindings between [`egui`](https://github.com/emilk/egui) and [wgpu](https://crates.io/crates/wgpu). +//! +//! ## Feature flags +#![cfg_attr(feature = "document-features", doc = document_features::document_features!())] +//! + +#![allow(unsafe_code)] + +pub use wgpu; + +/// Low-level painting of [`egui`](https://github.com/emilk/egui) on [`wgpu`]. +pub mod renderer; +pub use renderer::CallbackFn; +pub use renderer::Renderer; + +/// Module for painting [`egui`](https://github.com/emilk/egui) with [`wgpu`] on [`winit`]. +#[cfg(feature = "winit")] +pub mod winit; + +use std::sync::Arc; + +use epaint::mutex::RwLock; + +/// Access to the render state for egui. +#[derive(Clone)] +pub struct RenderState { + pub device: Arc, + pub queue: Arc, + pub target_format: wgpu::TextureFormat, + pub renderer: Arc>, +} + +/// Specifies which action should be taken as consequence of a [`wgpu::SurfaceError`] +pub enum SurfaceErrorAction { + /// Do nothing and skip the current frame. + SkipFrame, + + /// Instructs egui to recreate the surface, then skip the current frame. + RecreateSurface, +} + +/// Configuration for using wgpu with eframe or the egui-wgpu winit feature. +#[derive(Clone)] +pub struct WgpuConfiguration { + /// Configuration passed on device request. + pub device_descriptor: wgpu::DeviceDescriptor<'static>, + + /// Backends that should be supported (wgpu will pick one of these) + pub backends: wgpu::Backends, + + /// Present mode used for the primary surface. + pub present_mode: wgpu::PresentMode, + + /// Power preference for the adapter. + pub power_preference: wgpu::PowerPreference, + + /// Callback for surface errors. + pub on_surface_error: Arc SurfaceErrorAction>, + + pub depth_format: Option, +} + +impl Default for WgpuConfiguration { + fn default() -> Self { + Self { + device_descriptor: wgpu::DeviceDescriptor { + label: Some("egui wgpu device"), + features: wgpu::Features::default(), + limits: wgpu::Limits::default(), + }, + backends: wgpu::Backends::PRIMARY | wgpu::Backends::GL, + present_mode: wgpu::PresentMode::AutoVsync, + power_preference: wgpu::PowerPreference::HighPerformance, + depth_format: None, + + on_surface_error: Arc::new(|err| { + if err == wgpu::SurfaceError::Outdated { + // This error occurs when the app is minimized on Windows. + // Silently return here to prevent spamming the console with: + // "The underlying surface has changed, and therefore the swap chain must be updated" + } else { + tracing::warn!("Dropped frame with error: {err}"); + } + SurfaceErrorAction::SkipFrame + }), + } + } +} + +/// Find the framebuffer format that egui prefers +pub fn preferred_framebuffer_format(formats: &[wgpu::TextureFormat]) -> wgpu::TextureFormat { + for &format in formats { + if matches!( + format, + wgpu::TextureFormat::Rgba8Unorm | wgpu::TextureFormat::Bgra8Unorm + ) { + return format; + } + } + formats[0] // take the first +} +// maybe use this-error? +#[derive(Debug)] +pub enum WgpuError { + DeviceError(wgpu::RequestDeviceError), + SurfaceError(wgpu::CreateSurfaceError), +} + +impl std::fmt::Display for WgpuError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(self, f) + } +} + +impl std::error::Error for WgpuError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + WgpuError::DeviceError(e) => e.source(), + WgpuError::SurfaceError(e) => e.source(), + } + } +} + +impl From for WgpuError { + fn from(e: wgpu::RequestDeviceError) -> Self { + Self::DeviceError(e) + } +} + +impl From for WgpuError { + fn from(e: wgpu::CreateSurfaceError) -> Self { + Self::SurfaceError(e) + } +} +// --------------------------------------------------------------------------- + +/// Profiling macro for feature "puffin" +macro_rules! profile_function { + ($($arg: tt)*) => { + #[cfg(feature = "puffin")] + #[cfg(not(target_arch = "wasm32"))] + puffin::profile_function!($($arg)*); + }; +} +pub(crate) use profile_function; + +/// Profiling macro for feature "puffin" +macro_rules! profile_scope { + ($($arg: tt)*) => { + #[cfg(feature = "puffin")] + #[cfg(not(target_arch = "wasm32"))] + puffin::profile_scope!($($arg)*); + }; +} +pub(crate) use profile_scope; diff --git a/nevmes-gui/crates/egui-wgpu/src/renderer.rs b/nevmes-gui/crates/egui-wgpu/src/renderer.rs new file mode 100644 index 0000000..524d989 --- /dev/null +++ b/nevmes-gui/crates/egui-wgpu/src/renderer.rs @@ -0,0 +1,931 @@ +#![allow(unsafe_code)] + +use std::num::NonZeroU64; +use std::ops::Range; +use std::{borrow::Cow, collections::HashMap, num::NonZeroU32}; + +use type_map::concurrent::TypeMap; +use wgpu; +use wgpu::util::DeviceExt as _; + +use epaint::{emath::NumExt, PaintCallbackInfo, Primitive, Vertex}; + +/// A callback function that can be used to compose an [`epaint::PaintCallback`] for custom WGPU +/// rendering. +/// +/// The callback is composed of two functions: `prepare` and `paint`: +/// - `prepare` is called every frame before `paint`, and can use the passed-in +/// [`wgpu::Device`] and [`wgpu::Buffer`] to allocate or modify GPU resources such as buffers. +/// - `paint` is called after `prepare` and is given access to the [`wgpu::RenderPass`] so +/// that it can issue draw commands into the same [`wgpu::RenderPass`] that is used for +/// all other egui elements. +/// +/// The final argument of both the `prepare` and `paint` callbacks is a the +/// [`paint_callback_resources`][crate::renderer::Renderer::paint_callback_resources]. +/// `paint_callback_resources` has the same lifetime as the Egui render pass, so it can be used to +/// store buffers, pipelines, and other information that needs to be accessed during the render +/// pass. +/// +/// # Example +/// +/// See the [`custom3d_wgpu`](https://github.com/emilk/egui/blob/master/crates/egui_demo_app/src/apps/custom3d_wgpu.rs) demo source for a detailed usage example. +pub struct CallbackFn { + prepare: Box, + paint: Box, +} + +type PrepareCallback = dyn Fn( + &wgpu::Device, + &wgpu::Queue, + &mut wgpu::CommandEncoder, + &mut TypeMap, + ) -> Vec + + Sync + + Send; + +type PaintCallback = + dyn for<'a, 'b> Fn(PaintCallbackInfo, &'a mut wgpu::RenderPass<'b>, &'b TypeMap) + Sync + Send; + +impl Default for CallbackFn { + fn default() -> Self { + CallbackFn { + prepare: Box::new(|_, _, _, _| Vec::new()), + paint: Box::new(|_, _, _| ()), + } + } +} + +impl CallbackFn { + pub fn new() -> Self { + Self::default() + } + + /// Set the prepare callback. + /// + /// The passed-in `CommandEncoder` is egui's and can be used directly to register + /// wgpu commands for simple use cases. + /// This allows reusing the same [`wgpu::CommandEncoder`] for all callbacks and egui + /// rendering itself. + /// + /// For more complicated use cases, one can also return a list of arbitrary + /// `CommandBuffer`s and have complete control over how they get created and fed. + /// In particular, this gives an opportunity to parallelize command registration and + /// prevents a faulty callback from poisoning the main wgpu pipeline. + /// + /// When using eframe, the main egui command buffer, as well as all user-defined + /// command buffers returned by this function, are guaranteed to all be submitted + /// at once in a single call. + pub fn prepare(mut self, prepare: F) -> Self + where + F: Fn( + &wgpu::Device, + &wgpu::Queue, + &mut wgpu::CommandEncoder, + &mut TypeMap, + ) -> Vec + + Sync + + Send + + 'static, + { + self.prepare = Box::new(prepare) as _; + self + } + + /// Set the paint callback + pub fn paint(mut self, paint: F) -> Self + where + F: for<'a, 'b> Fn(PaintCallbackInfo, &'a mut wgpu::RenderPass<'b>, &'b TypeMap) + + Sync + + Send + + 'static, + { + self.paint = Box::new(paint) as _; + self + } +} + +/// Information about the screen used for rendering. +pub struct ScreenDescriptor { + /// Size of the window in physical pixels. + pub size_in_pixels: [u32; 2], + + /// HiDPI scale factor (pixels per point). + pub pixels_per_point: f32, +} + +impl ScreenDescriptor { + /// size in "logical" points + fn screen_size_in_points(&self) -> [f32; 2] { + [ + self.size_in_pixels[0] as f32 / self.pixels_per_point, + self.size_in_pixels[1] as f32 / self.pixels_per_point, + ] + } +} + +/// Uniform buffer used when rendering. +#[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable)] +#[repr(C)] +struct UniformBuffer { + screen_size_in_points: [f32; 2], + // Uniform buffers need to be at least 16 bytes in WebGL. + // See https://github.com/gfx-rs/wgpu/issues/2072 + _padding: [u32; 2], +} + +struct SlicedBuffer { + buffer: wgpu::Buffer, + slices: Vec>, + capacity: wgpu::BufferAddress, +} + +/// Renderer for a egui based GUI. +pub struct Renderer { + pipeline: wgpu::RenderPipeline, + + index_buffer: SlicedBuffer, + vertex_buffer: SlicedBuffer, + + uniform_buffer: wgpu::Buffer, + uniform_bind_group: wgpu::BindGroup, + texture_bind_group_layout: wgpu::BindGroupLayout, + + /// Map of egui texture IDs to textures and their associated bindgroups (texture view + + /// sampler). The texture may be None if the TextureId is just a handle to a user-provided + /// sampler. + textures: HashMap, wgpu::BindGroup)>, + next_user_texture_id: u64, + samplers: HashMap, + + /// Storage for use by [`epaint::PaintCallback`]'s that need to store resources such as render + /// pipelines that must have the lifetime of the renderpass. + pub paint_callback_resources: TypeMap, +} + +impl Renderer { + /// Creates a renderer for a egui UI. + /// + /// `output_color_format` should preferably be [`wgpu::TextureFormat::Rgba8Unorm`] or + /// [`wgpu::TextureFormat::Bgra8Unorm`], i.e. in gamma-space. + pub fn new( + device: &wgpu::Device, + output_color_format: wgpu::TextureFormat, + output_depth_format: Option, + msaa_samples: u32, + ) -> Self { + crate::profile_function!(); + + let shader = wgpu::ShaderModuleDescriptor { + label: Some("egui"), + source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("egui.wgsl"))), + }; + let module = device.create_shader_module(shader); + + let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("egui_uniform_buffer"), + contents: bytemuck::cast_slice(&[UniformBuffer { + screen_size_in_points: [0.0, 0.0], + _padding: Default::default(), + }]), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }); + + let uniform_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("egui_uniform_bind_group_layout"), + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX, + ty: wgpu::BindingType::Buffer { + has_dynamic_offset: false, + min_binding_size: NonZeroU64::new(std::mem::size_of::() as _), + ty: wgpu::BufferBindingType::Uniform, + }, + count: None, + }], + }); + + let uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("egui_uniform_bind_group"), + layout: &uniform_bind_group_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { + buffer: &uniform_buffer, + offset: 0, + size: None, + }), + }], + }); + + let texture_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("egui_texture_bind_group_layout"), + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + multisampled: false, + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + ], + }); + + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("egui_pipeline_layout"), + bind_group_layouts: &[&uniform_bind_group_layout, &texture_bind_group_layout], + push_constant_ranges: &[], + }); + + let depth_stencil = output_depth_format.map(|format| wgpu::DepthStencilState { + format, + depth_write_enabled: false, + depth_compare: wgpu::CompareFunction::Always, + stencil: wgpu::StencilState::default(), + bias: wgpu::DepthBiasState::default(), + }); + + let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("egui_pipeline"), + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + entry_point: "vs_main", + module: &module, + buffers: &[wgpu::VertexBufferLayout { + array_stride: 5 * 4, + step_mode: wgpu::VertexStepMode::Vertex, + // 0: vec2 position + // 1: vec2 texture coordinates + // 2: uint color + attributes: &wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2, 2 => Uint32], + }], + }, + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + unclipped_depth: false, + conservative: false, + cull_mode: None, + front_face: wgpu::FrontFace::default(), + polygon_mode: wgpu::PolygonMode::default(), + strip_index_format: None, + }, + depth_stencil, + multisample: wgpu::MultisampleState { + alpha_to_coverage_enabled: false, + count: msaa_samples, + mask: !0, + }, + + fragment: Some(wgpu::FragmentState { + module: &module, + entry_point: if output_color_format.describe().srgb { + tracing::warn!("Detected a linear (sRGBA aware) framebuffer {:?}. egui prefers Rgba8Unorm or Bgra8Unorm", output_color_format); + "fs_main_linear_framebuffer" + } else { + "fs_main_gamma_framebuffer" // this is what we prefer + }, + targets: &[Some(wgpu::ColorTargetState { + format: output_color_format, + blend: Some(wgpu::BlendState { + color: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::One, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + alpha: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::OneMinusDstAlpha, + dst_factor: wgpu::BlendFactor::One, + operation: wgpu::BlendOperation::Add, + }, + }), + write_mask: wgpu::ColorWrites::ALL, + })], + }), + multiview: None, + }); + + const VERTEX_BUFFER_START_CAPACITY: wgpu::BufferAddress = + (std::mem::size_of::() * 1024) as _; + const INDEX_BUFFER_START_CAPACITY: wgpu::BufferAddress = + (std::mem::size_of::() * 1024 * 3) as _; + + Self { + pipeline, + vertex_buffer: SlicedBuffer { + buffer: create_vertex_buffer(device, VERTEX_BUFFER_START_CAPACITY), + slices: Vec::with_capacity(64), + capacity: VERTEX_BUFFER_START_CAPACITY, + }, + index_buffer: SlicedBuffer { + buffer: create_index_buffer(device, INDEX_BUFFER_START_CAPACITY), + slices: Vec::with_capacity(64), + capacity: INDEX_BUFFER_START_CAPACITY, + }, + uniform_buffer, + uniform_bind_group, + texture_bind_group_layout, + textures: HashMap::new(), + next_user_texture_id: 0, + samplers: HashMap::new(), + paint_callback_resources: TypeMap::default(), + } + } + + /// Executes the egui renderer onto an existing wgpu renderpass. + pub fn render<'rp>( + &'rp self, + render_pass: &mut wgpu::RenderPass<'rp>, + paint_jobs: &[epaint::ClippedPrimitive], + screen_descriptor: &ScreenDescriptor, + ) { + crate::profile_function!(); + + let pixels_per_point = screen_descriptor.pixels_per_point; + let size_in_pixels = screen_descriptor.size_in_pixels; + + // Whether or not we need to reset the render pass because a paint callback has just + // run. + let mut needs_reset = true; + + let mut index_buffer_slices = self.index_buffer.slices.iter(); + let mut vertex_buffer_slices = self.vertex_buffer.slices.iter(); + + for epaint::ClippedPrimitive { + clip_rect, + primitive, + } in paint_jobs + { + if needs_reset { + render_pass.set_viewport( + 0.0, + 0.0, + size_in_pixels[0] as f32, + size_in_pixels[1] as f32, + 0.0, + 1.0, + ); + render_pass.set_pipeline(&self.pipeline); + render_pass.set_bind_group(0, &self.uniform_bind_group, &[]); + needs_reset = false; + } + + { + let rect = ScissorRect::new(clip_rect, pixels_per_point, size_in_pixels); + + if rect.width == 0 || rect.height == 0 { + // Skip rendering zero-sized clip areas. + if let Primitive::Mesh(_) = primitive { + // If this is a mesh, we need to advance the index and vertex buffer iterators: + index_buffer_slices.next().unwrap(); + vertex_buffer_slices.next().unwrap(); + } + continue; + } + + render_pass.set_scissor_rect(rect.x, rect.y, rect.width, rect.height); + } + + match primitive { + Primitive::Mesh(mesh) => { + let index_buffer_slice = index_buffer_slices.next().unwrap(); + let vertex_buffer_slice = vertex_buffer_slices.next().unwrap(); + + if let Some((_texture, bind_group)) = self.textures.get(&mesh.texture_id) { + render_pass.set_bind_group(1, bind_group, &[]); + render_pass.set_index_buffer( + self.index_buffer.buffer.slice(index_buffer_slice.clone()), + wgpu::IndexFormat::Uint32, + ); + render_pass.set_vertex_buffer( + 0, + self.vertex_buffer.buffer.slice(vertex_buffer_slice.clone()), + ); + render_pass.draw_indexed(0..mesh.indices.len() as u32, 0, 0..1); + } else { + tracing::warn!("Missing texture: {:?}", mesh.texture_id); + } + } + Primitive::Callback(callback) => { + let cbfn = if let Some(c) = callback.callback.downcast_ref::() { + c + } else { + // We already warned in the `prepare` callback + continue; + }; + + if callback.rect.is_positive() { + crate::profile_scope!("callback"); + + needs_reset = true; + + { + // We're setting a default viewport for the render pass as a + // courtesy for the user, so that they don't have to think about + // it in the simple case where they just want to fill the whole + // paint area. + // + // The user still has the possibility of setting their own custom + // viewport during the paint callback, effectively overriding this + // one. + + let min = (callback.rect.min.to_vec2() * pixels_per_point).round(); + let max = (callback.rect.max.to_vec2() * pixels_per_point).round(); + + render_pass.set_viewport( + min.x, + min.y, + max.x - min.x, + max.y - min.y, + 0.0, + 1.0, + ); + } + + (cbfn.paint)( + PaintCallbackInfo { + viewport: callback.rect, + clip_rect: *clip_rect, + pixels_per_point, + screen_size_px: size_in_pixels, + }, + render_pass, + &self.paint_callback_resources, + ); + } + } + } + } + + render_pass.set_scissor_rect(0, 0, size_in_pixels[0], size_in_pixels[1]); + } + + /// Should be called before `render()`. + pub fn update_texture( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + id: epaint::TextureId, + image_delta: &epaint::ImageDelta, + ) { + crate::profile_function!(); + + let width = image_delta.image.width() as u32; + let height = image_delta.image.height() as u32; + + let size = wgpu::Extent3d { + width, + height, + depth_or_array_layers: 1, + }; + + let data_color32 = match &image_delta.image { + epaint::ImageData::Color(image) => { + assert_eq!( + width as usize * height as usize, + image.pixels.len(), + "Mismatch between texture size and texel count" + ); + Cow::Borrowed(&image.pixels) + } + epaint::ImageData::Font(image) => { + assert_eq!( + width as usize * height as usize, + image.pixels.len(), + "Mismatch between texture size and texel count" + ); + Cow::Owned(image.srgba_pixels(None).collect::>()) + } + }; + let data_bytes: &[u8] = bytemuck::cast_slice(data_color32.as_slice()); + + let queue_write_data_to_texture = |texture, origin| { + queue.write_texture( + wgpu::ImageCopyTexture { + texture, + mip_level: 0, + origin, + aspect: wgpu::TextureAspect::All, + }, + data_bytes, + wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: NonZeroU32::new(4 * width), + rows_per_image: NonZeroU32::new(height), + }, + size, + ); + }; + + if let Some(pos) = image_delta.pos { + // update the existing texture + let (texture, _bind_group) = self + .textures + .get(&id) + .expect("Tried to update a texture that has not been allocated yet."); + let origin = wgpu::Origin3d { + x: pos[0] as u32, + y: pos[1] as u32, + z: 0, + }; + queue_write_data_to_texture( + texture.as_ref().expect("Tried to update user texture."), + origin, + ); + } else { + // allocate a new texture + // Use same label for all resources associated with this texture id (no point in retyping the type) + let label_str = format!("egui_texid_{:?}", id); + let label = Some(label_str.as_str()); + let texture = device.create_texture(&wgpu::TextureDescriptor { + label, + size, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8UnormSrgb, // Minspec for wgpu WebGL emulation is WebGL2, so this should always be supported. + usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, + view_formats: &[wgpu::TextureFormat::Rgba8UnormSrgb], + }); + let sampler = self + .samplers + .entry(image_delta.options) + .or_insert_with(|| create_sampler(image_delta.options, device)); + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label, + layout: &self.texture_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView( + &texture.create_view(&wgpu::TextureViewDescriptor::default()), + ), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(sampler), + }, + ], + }); + let origin = wgpu::Origin3d::ZERO; + queue_write_data_to_texture(&texture, origin); + self.textures.insert(id, (Some(texture), bind_group)); + }; + } + + pub fn free_texture(&mut self, id: &epaint::TextureId) { + self.textures.remove(id); + } + + /// Get the WGPU texture and bind group associated to a texture that has been allocated by egui. + /// + /// This could be used by custom paint hooks to render images that have been added through with + /// [`egui_extras::RetainedImage`](https://docs.rs/egui_extras/latest/egui_extras/image/struct.RetainedImage.html) + /// or [`epaint::Context::load_texture`](https://docs.rs/egui/latest/egui/struct.Context.html#method.load_texture). + pub fn texture( + &self, + id: &epaint::TextureId, + ) -> Option<&(Option, wgpu::BindGroup)> { + self.textures.get(id) + } + + /// Registers a `wgpu::Texture` with a `epaint::TextureId`. + /// + /// This enables the application to reference the texture inside an image ui element. + /// This effectively enables off-screen rendering inside the egui UI. Texture must have + /// the texture format `TextureFormat::Rgba8UnormSrgb` and + /// Texture usage `TextureUsage::SAMPLED`. + pub fn register_native_texture( + &mut self, + device: &wgpu::Device, + texture: &wgpu::TextureView, + texture_filter: wgpu::FilterMode, + ) -> epaint::TextureId { + self.register_native_texture_with_sampler_options( + device, + texture, + wgpu::SamplerDescriptor { + label: Some(format!("egui_user_image_{}", self.next_user_texture_id).as_str()), + mag_filter: texture_filter, + min_filter: texture_filter, + ..Default::default() + }, + ) + } + + /// Registers a `wgpu::Texture` with an existing `epaint::TextureId`. + /// + /// This enables applications to reuse `TextureId`s. + pub fn update_egui_texture_from_wgpu_texture( + &mut self, + device: &wgpu::Device, + texture: &wgpu::TextureView, + texture_filter: wgpu::FilterMode, + id: epaint::TextureId, + ) { + self.update_egui_texture_from_wgpu_texture_with_sampler_options( + device, + texture, + wgpu::SamplerDescriptor { + label: Some(format!("egui_user_image_{}", self.next_user_texture_id).as_str()), + mag_filter: texture_filter, + min_filter: texture_filter, + ..Default::default() + }, + id, + ); + } + + /// Registers a `wgpu::Texture` with a `epaint::TextureId` while also accepting custom + /// `wgpu::SamplerDescriptor` options. + /// + /// This allows applications to specify individual minification/magnification filters as well as + /// custom mipmap and tiling options. + /// + /// The `Texture` must have the format `TextureFormat::Rgba8UnormSrgb` and usage + /// `TextureUsage::SAMPLED`. Any compare function supplied in the `SamplerDescriptor` will be + /// ignored. + #[allow(clippy::needless_pass_by_value)] // false positive + pub fn register_native_texture_with_sampler_options( + &mut self, + device: &wgpu::Device, + texture: &wgpu::TextureView, + sampler_descriptor: wgpu::SamplerDescriptor<'_>, + ) -> epaint::TextureId { + crate::profile_function!(); + + let sampler = device.create_sampler(&wgpu::SamplerDescriptor { + compare: None, + ..sampler_descriptor + }); + + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some(format!("egui_user_image_{}", self.next_user_texture_id).as_str()), + layout: &self.texture_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(texture), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&sampler), + }, + ], + }); + + let id = epaint::TextureId::User(self.next_user_texture_id); + self.textures.insert(id, (None, bind_group)); + self.next_user_texture_id += 1; + + id + } + + /// Registers a `wgpu::Texture` with an existing `epaint::TextureId` while also accepting custom + /// `wgpu::SamplerDescriptor` options. + /// + /// This allows applications to reuse `TextureId`s created with custom sampler options. + #[allow(clippy::needless_pass_by_value)] // false positive + pub fn update_egui_texture_from_wgpu_texture_with_sampler_options( + &mut self, + device: &wgpu::Device, + texture: &wgpu::TextureView, + sampler_descriptor: wgpu::SamplerDescriptor<'_>, + id: epaint::TextureId, + ) { + crate::profile_function!(); + + let (_user_texture, user_texture_binding) = self + .textures + .get_mut(&id) + .expect("Tried to update a texture that has not been allocated yet."); + + let sampler = device.create_sampler(&wgpu::SamplerDescriptor { + compare: None, + ..sampler_descriptor + }); + + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some(format!("egui_user_image_{}", self.next_user_texture_id).as_str()), + layout: &self.texture_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(texture), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&sampler), + }, + ], + }); + + *user_texture_binding = bind_group; + } + + /// Uploads the uniform, vertex and index data used by the renderer. + /// Should be called before `render()`. + /// + /// Returns all user-defined command buffers gathered from prepare callbacks. + pub fn update_buffers( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + encoder: &mut wgpu::CommandEncoder, + paint_jobs: &[epaint::ClippedPrimitive], + screen_descriptor: &ScreenDescriptor, + ) -> Vec { + crate::profile_function!(); + + let screen_size_in_points = screen_descriptor.screen_size_in_points(); + + { + crate::profile_scope!("uniforms"); + // Update uniform buffer + queue.write_buffer( + &self.uniform_buffer, + 0, + bytemuck::cast_slice(&[UniformBuffer { + screen_size_in_points, + _padding: Default::default(), + }]), + ); + } + + // Determine how many vertices & indices need to be rendered. + let (vertex_count, index_count) = { + crate::profile_scope!("count_vertices_indices"); + paint_jobs.iter().fold((0, 0), |acc, clipped_primitive| { + match &clipped_primitive.primitive { + Primitive::Mesh(mesh) => { + (acc.0 + mesh.vertices.len(), acc.1 + mesh.indices.len()) + } + Primitive::Callback(_) => acc, + } + }) + }; + + { + // Resize index buffer if needed: + self.index_buffer.slices.clear(); + let required_size = (std::mem::size_of::() * index_count) as u64; + if self.index_buffer.capacity < required_size { + self.index_buffer.capacity = + (self.index_buffer.capacity * 2).at_least(required_size); + self.index_buffer.buffer = create_index_buffer(device, self.index_buffer.capacity); + } + } + + { + // Resize vertex buffer if needed: + self.vertex_buffer.slices.clear(); + let required_size = (std::mem::size_of::() * vertex_count) as u64; + if self.vertex_buffer.capacity < required_size { + self.vertex_buffer.capacity = + (self.vertex_buffer.capacity * 2).at_least(required_size); + self.vertex_buffer.buffer = + create_vertex_buffer(device, self.vertex_buffer.capacity); + } + } + + // Upload index & vertex data and call user callbacks + let mut user_cmd_bufs = Vec::new(); // collect user command buffers + + crate::profile_scope!("primitives"); + for epaint::ClippedPrimitive { primitive, .. } in paint_jobs.iter() { + match primitive { + Primitive::Mesh(mesh) => { + { + let index_offset = self.index_buffer.slices.last().unwrap_or(&(0..0)).end; + let data = bytemuck::cast_slice(&mesh.indices); + queue.write_buffer(&self.index_buffer.buffer, index_offset, data); + self.index_buffer + .slices + .push(index_offset..(data.len() as wgpu::BufferAddress + index_offset)); + } + { + let vertex_offset = self.vertex_buffer.slices.last().unwrap_or(&(0..0)).end; + let data = bytemuck::cast_slice(&mesh.vertices); + queue.write_buffer(&self.vertex_buffer.buffer, vertex_offset, data); + self.vertex_buffer.slices.push( + vertex_offset..(data.len() as wgpu::BufferAddress + vertex_offset), + ); + } + } + Primitive::Callback(callback) => { + let cbfn = if let Some(c) = callback.callback.downcast_ref::() { + c + } else { + tracing::warn!("Unknown paint callback: expected `egui_wgpu::CallbackFn`"); + continue; + }; + + crate::profile_scope!("callback"); + user_cmd_bufs.extend((cbfn.prepare)( + device, + queue, + encoder, + &mut self.paint_callback_resources, + )); + } + } + } + + user_cmd_bufs + } +} + +fn create_sampler( + options: epaint::textures::TextureOptions, + device: &wgpu::Device, +) -> wgpu::Sampler { + let mag_filter = match options.magnification { + epaint::textures::TextureFilter::Nearest => wgpu::FilterMode::Nearest, + epaint::textures::TextureFilter::Linear => wgpu::FilterMode::Linear, + }; + let min_filter = match options.minification { + epaint::textures::TextureFilter::Nearest => wgpu::FilterMode::Nearest, + epaint::textures::TextureFilter::Linear => wgpu::FilterMode::Linear, + }; + device.create_sampler(&wgpu::SamplerDescriptor { + label: Some(&format!( + "egui sampler (mag: {:?}, min {:?})", + mag_filter, min_filter + )), + mag_filter, + min_filter, + ..Default::default() + }) +} + +fn create_vertex_buffer(device: &wgpu::Device, size: u64) -> wgpu::Buffer { + crate::profile_function!(); + device.create_buffer(&wgpu::BufferDescriptor { + label: Some("egui_vertex_buffer"), + usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + size, + mapped_at_creation: false, + }) +} + +fn create_index_buffer(device: &wgpu::Device, size: u64) -> wgpu::Buffer { + crate::profile_function!(); + device.create_buffer(&wgpu::BufferDescriptor { + label: Some("egui_index_buffer"), + usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST, + size, + mapped_at_creation: false, + }) +} + +/// A Rect in physical pixel space, used for setting cliipping rectangles. +struct ScissorRect { + x: u32, + y: u32, + width: u32, + height: u32, +} + +impl ScissorRect { + fn new(clip_rect: &epaint::Rect, pixels_per_point: f32, target_size: [u32; 2]) -> Self { + // Transform clip rect to physical pixels: + let clip_min_x = pixels_per_point * clip_rect.min.x; + let clip_min_y = pixels_per_point * clip_rect.min.y; + let clip_max_x = pixels_per_point * clip_rect.max.x; + let clip_max_y = pixels_per_point * clip_rect.max.y; + + // Round to integer: + let clip_min_x = clip_min_x.round() as u32; + let clip_min_y = clip_min_y.round() as u32; + let clip_max_x = clip_max_x.round() as u32; + let clip_max_y = clip_max_y.round() as u32; + + // Clamp: + let clip_min_x = clip_min_x.clamp(0, target_size[0]); + let clip_min_y = clip_min_y.clamp(0, target_size[1]); + let clip_max_x = clip_max_x.clamp(clip_min_x, target_size[0]); + let clip_max_y = clip_max_y.clamp(clip_min_y, target_size[1]); + + Self { + x: clip_min_x, + y: clip_min_y, + width: clip_max_x - clip_min_x, + height: clip_max_y - clip_min_y, + } + } +} + +#[test] +fn renderer_impl_send_sync() { + fn assert_send_sync() {} + assert_send_sync::(); +} diff --git a/nevmes-gui/crates/egui-wgpu/src/winit.rs b/nevmes-gui/crates/egui-wgpu/src/winit.rs new file mode 100644 index 0000000..c6ef22b --- /dev/null +++ b/nevmes-gui/crates/egui-wgpu/src/winit.rs @@ -0,0 +1,418 @@ +use std::sync::Arc; + +use epaint::mutex::RwLock; + +use tracing::error; + +use crate::{renderer, RenderState, Renderer, SurfaceErrorAction, WgpuConfiguration}; + +struct SurfaceState { + surface: wgpu::Surface, + alpha_mode: wgpu::CompositeAlphaMode, + width: u32, + height: u32, +} + +/// Everything you need to paint egui with [`wgpu`] on [`winit`]. +/// +/// Alternatively you can use [`crate::renderer`] directly. +pub struct Painter { + configuration: WgpuConfiguration, + msaa_samples: u32, + support_transparent_backbuffer: bool, + depth_format: Option, + depth_texture_view: Option, + + instance: wgpu::Instance, + adapter: Option, + render_state: Option, + surface_state: Option, +} + +impl Painter { + /// Manages [`wgpu`] state, including surface state, required to render egui. + /// + /// Only the [`wgpu::Instance`] is initialized here. Device selection and the initialization + /// of render + surface state is deferred until the painter is given its first window target + /// via [`set_window()`](Self::set_window). (Ensuring that a device that's compatible with the + /// native window is chosen) + /// + /// Before calling [`paint_and_update_textures()`](Self::paint_and_update_textures) a + /// [`wgpu::Surface`] must be initialized (and corresponding render state) by calling + /// [`set_window()`](Self::set_window) once you have + /// a [`winit::window::Window`] with a valid `.raw_window_handle()` + /// associated. + pub fn new( + configuration: WgpuConfiguration, + msaa_samples: u32, + depth_bits: u8, + support_transparent_backbuffer: bool, + ) -> Self { + let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { + backends: configuration.backends, + dx12_shader_compiler: Default::default(), // + }); + + Self { + configuration, + msaa_samples, + support_transparent_backbuffer, + depth_format: (depth_bits > 0).then_some(wgpu::TextureFormat::Depth32Float), + depth_texture_view: None, + + instance, + adapter: None, + render_state: None, + surface_state: None, + } + } + + /// Get the [`RenderState`]. + /// + /// Will return [`None`] if the render state has not been initialized yet. + pub fn render_state(&self) -> Option { + self.render_state.clone() + } + + async fn init_render_state( + &self, + adapter: &wgpu::Adapter, + target_format: wgpu::TextureFormat, + ) -> Result { + adapter + .request_device(&self.configuration.device_descriptor, None) + .await + .map(|(device, queue)| { + let renderer = + Renderer::new(&device, target_format, self.depth_format, self.msaa_samples); + RenderState { + device: Arc::new(device), + queue: Arc::new(queue), + target_format, + renderer: Arc::new(RwLock::new(renderer)), + } + }) + } + + // We want to defer the initialization of our render state until we have a surface + // so we can take its format into account. + // + // After we've initialized our render state once though we expect all future surfaces + // will have the same format and so this render state will remain valid. + async fn ensure_render_state_for_surface( + &mut self, + surface: &wgpu::Surface, + ) -> Result<(), wgpu::RequestDeviceError> { + if self.adapter.is_none() { + self.adapter = self + .instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: self.configuration.power_preference, + compatible_surface: Some(surface), + force_fallback_adapter: false, + }) + .await; + } + if self.render_state.is_none() { + match &self.adapter { + Some(adapter) => { + let swapchain_format = crate::preferred_framebuffer_format( + &surface.get_capabilities(adapter).formats, + ); + let rs = self.init_render_state(adapter, swapchain_format).await?; + self.render_state = Some(rs); + } + None => return Err(wgpu::RequestDeviceError {}), + } + } + Ok(()) + } + + fn configure_surface( + surface_state: &SurfaceState, + render_state: &RenderState, + present_mode: wgpu::PresentMode, + ) { + surface_state.surface.configure( + &render_state.device, + &wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: render_state.target_format, + width: surface_state.width, + height: surface_state.height, + present_mode, + alpha_mode: surface_state.alpha_mode, + view_formats: vec![render_state.target_format], + }, + ); + } + + /// Updates (or clears) the [`winit::window::Window`] associated with the [`Painter`] + /// + /// This creates a [`wgpu::Surface`] for the given Window (as well as initializing render + /// state if needed) that is used for egui rendering. + /// + /// This must be called before trying to render via + /// [`paint_and_update_textures`](Self::paint_and_update_textures) + /// + /// # Portability + /// + /// _In particular it's important to note that on Android a it's only possible to create + /// a window surface between `Resumed` and `Paused` lifecycle events, and Winit will panic on + /// attempts to query the raw window handle while paused._ + /// + /// On Android [`set_window`](Self::set_window) should be called with `Some(window)` for each + /// `Resumed` event and `None` for each `Paused` event. Currently, on all other platforms + /// [`set_window`](Self::set_window) may be called with `Some(window)` as soon as you have a + /// valid [`winit::window::Window`]. + /// + /// # Safety + /// + /// The raw Window handle associated with the given `window` must be a valid object to create a + /// surface upon and must remain valid for the lifetime of the created surface. (The surface may + /// be cleared by passing `None`). + /// + /// # Errors + /// If the provided wgpu configuration does not match an available device. + pub async unsafe fn set_window( + &mut self, + window: Option<&winit::window::Window>, + ) -> Result<(), crate::WgpuError> { + match window { + Some(window) => { + let surface = self.instance.create_surface(&window)?; + + self.ensure_render_state_for_surface(&surface).await?; + + let alpha_mode = if self.support_transparent_backbuffer { + let supported_alpha_modes = surface + .get_capabilities(self.adapter.as_ref().unwrap()) + .alpha_modes; + + // Prefer pre multiplied over post multiplied! + if supported_alpha_modes.contains(&wgpu::CompositeAlphaMode::PreMultiplied) { + wgpu::CompositeAlphaMode::PreMultiplied + } else if supported_alpha_modes + .contains(&wgpu::CompositeAlphaMode::PostMultiplied) + { + wgpu::CompositeAlphaMode::PostMultiplied + } else { + tracing::warn!("Transparent window was requested, but the active wgpu surface does not support a `CompositeAlphaMode` with transparency."); + wgpu::CompositeAlphaMode::Auto + } + } else { + wgpu::CompositeAlphaMode::Auto + }; + + let size = window.inner_size(); + self.surface_state = Some(SurfaceState { + surface, + width: size.width, + height: size.height, + alpha_mode, + }); + self.resize_and_generate_depth_texture_view(size.width, size.height); + } + None => { + self.surface_state = None; + } + } + Ok(()) + } + + /// Returns the maximum texture dimension supported if known + /// + /// This API will only return a known dimension after `set_window()` has been called + /// at least once, since the underlying device and render state are initialized lazily + /// once we have a window (that may determine the choice of adapter/device). + pub fn max_texture_side(&self) -> Option { + self.render_state + .as_ref() + .map(|rs| rs.device.limits().max_texture_dimension_2d as usize) + } + + fn resize_and_generate_depth_texture_view( + &mut self, + width_in_pixels: u32, + height_in_pixels: u32, + ) { + let render_state = self.render_state.as_ref().unwrap(); + let surface_state = self.surface_state.as_mut().unwrap(); + + surface_state.width = width_in_pixels; + surface_state.height = height_in_pixels; + + Self::configure_surface(surface_state, render_state, self.configuration.present_mode); + + self.depth_texture_view = self.depth_format.map(|depth_format| { + render_state + .device + .create_texture(&wgpu::TextureDescriptor { + label: Some("egui_depth_texture"), + size: wgpu::Extent3d { + width: width_in_pixels, + height: height_in_pixels, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: depth_format, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT + | wgpu::TextureUsages::TEXTURE_BINDING, + view_formats: &[depth_format], + }) + .create_view(&wgpu::TextureViewDescriptor::default()) + }); + } + + pub fn on_window_resized(&mut self, width_in_pixels: u32, height_in_pixels: u32) { + if self.surface_state.is_some() { + self.resize_and_generate_depth_texture_view(width_in_pixels, height_in_pixels); + } else { + error!("Ignoring window resize notification with no surface created via Painter::set_window()"); + } + } + + pub fn paint_and_update_textures( + &mut self, + pixels_per_point: f32, + clear_color: [f32; 4], + clipped_primitives: &[epaint::ClippedPrimitive], + textures_delta: &epaint::textures::TexturesDelta, + ) { + crate::profile_function!(); + + let render_state = match self.render_state.as_mut() { + Some(rs) => rs, + None => return, + }; + let surface_state = match self.surface_state.as_ref() { + Some(rs) => rs, + None => return, + }; + + let output_frame = { + crate::profile_scope!("get_current_texture"); + // This is what vsync-waiting happens, at least on Mac. + surface_state.surface.get_current_texture() + }; + + let output_frame = match output_frame { + Ok(frame) => frame, + #[allow(clippy::single_match_else)] + Err(e) => match (*self.configuration.on_surface_error)(e) { + SurfaceErrorAction::RecreateSurface => { + Self::configure_surface( + surface_state, + render_state, + self.configuration.present_mode, + ); + return; + } + SurfaceErrorAction::SkipFrame => { + return; + } + }, + }; + + let mut encoder = + render_state + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("encoder"), + }); + + // Upload all resources for the GPU. + let screen_descriptor = renderer::ScreenDescriptor { + size_in_pixels: [surface_state.width, surface_state.height], + pixels_per_point, + }; + + let user_cmd_bufs = { + let mut renderer = render_state.renderer.write(); + for (id, image_delta) in &textures_delta.set { + renderer.update_texture( + &render_state.device, + &render_state.queue, + *id, + image_delta, + ); + } + + renderer.update_buffers( + &render_state.device, + &render_state.queue, + &mut encoder, + clipped_primitives, + &screen_descriptor, + ) + }; + + { + let renderer = render_state.renderer.read(); + let frame_view = output_frame + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &frame_view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color { + r: clear_color[0] as f64, + g: clear_color[1] as f64, + b: clear_color[2] as f64, + a: clear_color[3] as f64, + }), + store: true, + }, + })], + depth_stencil_attachment: self.depth_texture_view.as_ref().map(|view| { + wgpu::RenderPassDepthStencilAttachment { + view, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Clear(1.0), + store: true, + }), + stencil_ops: None, + } + }), + label: Some("egui_render"), + }); + + renderer.render(&mut render_pass, clipped_primitives, &screen_descriptor); + } + + { + let mut renderer = render_state.renderer.write(); + for id in &textures_delta.free { + renderer.free_texture(id); + } + } + + let encoded = { + crate::profile_scope!("CommandEncoder::finish"); + encoder.finish() + }; + + // Submit the commands: both the main buffer and user-defined ones. + { + crate::profile_scope!("Queue::submit"); + render_state + .queue + .submit(user_cmd_bufs.into_iter().chain(std::iter::once(encoded))); + }; + + // Redraw egui + { + crate::profile_scope!("present"); + output_frame.present(); + } + } + + #[allow(clippy::unused_self)] + pub fn destroy(&mut self) { + // TODO(emilk): something here? + } +} diff --git a/nevmes-gui/crates/egui-winit/CHANGELOG.md b/nevmes-gui/crates/egui-winit/CHANGELOG.md new file mode 100644 index 0000000..0510d49 --- /dev/null +++ b/nevmes-gui/crates/egui-winit/CHANGELOG.md @@ -0,0 +1,64 @@ +# Changelog for egui-winit +All notable changes to the `egui-winit` integration will be noted in this file. + + +## Unreleased + + +## 0.21.1 - 2023-02-12 +* Fixed crash when window position is in an invalid state, which could happen e.g. due to changes in monitor size or DPI ([#2722](https://github.com/emilk/egui/issues/2722)). + + +## 0.21.0 - 2023-02-08 +* Fixed persistence of native window position on Windows OS ([#2583](https://github.com/emilk/egui/issues/2583)). +* Update to `winit` 0.28, adding support for mac trackpad zoom ([#2654](https://github.com/emilk/egui/pull/2654)). +* Remove the `screen_reader` feature. Use the `accesskit` feature flag instead ([#2669](https://github.com/emilk/egui/pull/2669)). +* Fix bug where the cursor could get stuck using the wrong icon. + + +## 0.20.1 - 2022-12-11 +* Fix [docs.rs](https://docs.rs/egui-winit) build ([#2420](https://github.com/emilk/egui/pull/2420)). + + +## 0.20.0 - 2022-12-08 +* The default features of the `winit` crate are not enabled if the default features of `egui-winit` are disabled too ([#1971](https://github.com/emilk/egui/pull/1971)). +* Added new feature `wayland` which enables Wayland support ([#1971](https://github.com/emilk/egui/pull/1971)). +* Don't repaint when just moving window ([#1980](https://github.com/emilk/egui/pull/1980)). +* Added optional integration with [AccessKit](https://accesskit.dev/) for implementing platform accessibility APIs ([#2294](https://github.com/emilk/egui/pull/2294)). + +## 0.19.0 - 2022-08-20 +* MSRV (Minimum Supported Rust Version) is now `1.61.0` ([#1846](https://github.com/emilk/egui/pull/1846)). +* Fixed clipboard on Wayland ([#1613](https://github.com/emilk/egui/pull/1613)). +* Allow deferred render + surface state initialization for Android ([#1634](https://github.com/emilk/egui/pull/1634)). +* Fixed window position persistence ([#1745](https://github.com/emilk/egui/pull/1745)). +* Fixed mouse cursor change on Linux ([#1747](https://github.com/emilk/egui/pull/1747)). +* Use the new `RawInput::has_focus` field to indicate whether the window has the keyboard focus ([#1859](https://github.com/emilk/egui/pull/1859)). + + +## 0.18.0 - 2022-04-30 +* Reexport `egui` crate +* MSRV (Minimum Supported Rust Version) is now `1.60.0` ([#1467](https://github.com/emilk/egui/pull/1467)). +* Added new feature `puffin` to add [`puffin profiler`](https://github.com/EmbarkStudios/puffin) scopes ([#1483](https://github.com/emilk/egui/pull/1483)). +* Renamed the feature `convert_bytemuck` to `bytemuck` ([#1467](https://github.com/emilk/egui/pull/1467)). +* Renamed the feature `serialize` to `serde` ([#1467](https://github.com/emilk/egui/pull/1467)). +* Removed the features `dark-light` and `persistence` ([#1542](https://github.com/emilk/egui/pull/1542)). + + +## 0.17.0 - 2022-02-22 +* Fixed horizontal scrolling direction on Linux. +* Replaced `std::time::Instant` with `instant::Instant` for WebAssembly compatability ([#1023](https://github.com/emilk/egui/pull/1023)) +* Automatically detect and apply dark or light mode from system ([#1045](https://github.com/emilk/egui/pull/1045)). +* Fixed `enable_drag` on Windows OS ([#1108](https://github.com/emilk/egui/pull/1108)). +* Shift-scroll will now result in horizontal scrolling on all platforms ([#1136](https://github.com/emilk/egui/pull/1136)). +* Require knowledge about max texture side (e.g. `GL_MAX_TEXTURE_SIZE`)) ([#1154](https://github.com/emilk/egui/pull/1154)). + + +## 0.16.0 - 2021-12-29 +* Added helper `EpiIntegration` ([#871](https://github.com/emilk/egui/pull/871)). +* Fixed shift key getting stuck enabled with the X11 option `shift:both_capslock` enabled ([#849](https://github.com/emilk/egui/pull/849)). +* Removed `State::is_quit_event` and `State::is_quit_shortcut` ([#881](https://github.com/emilk/egui/pull/881)). +* Updated `winit` to 0.26 ([#930](https://github.com/emilk/egui/pull/930)). + + +## 0.15.0 - 2021-10-24 +First stand-alone release. Previously part of `egui_glium`. diff --git a/nevmes-gui/crates/egui-winit/Cargo.toml b/nevmes-gui/crates/egui-winit/Cargo.toml new file mode 100644 index 0000000..9056238 --- /dev/null +++ b/nevmes-gui/crates/egui-winit/Cargo.toml @@ -0,0 +1,76 @@ +[package] +name = "egui-winit" +version = "0.21.1" +authors = ["Emil Ernerfeldt "] +description = "Bindings for using egui with winit" +edition = "2021" +rust-version = "1.65" +homepage = "https://github.com/emilk/egui/tree/master/crates/egui-winit" +license = "MIT OR Apache-2.0" +readme = "README.md" +repository = "https://github.com/emilk/egui/tree/master/crates/egui-winit" +categories = ["gui", "game-development"] +keywords = ["winit", "egui", "gui", "gamedev"] +include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"] + +[package.metadata.docs.rs] +all-features = true + + +[features] +default = ["clipboard", "links", "wayland", "winit/default"] + +## Enable platform accessibility API implementations through [AccessKit](https://accesskit.dev/). +accesskit = ["accesskit_winit", "egui/accesskit"] + +## [`bytemuck`](https://docs.rs/bytemuck) enables you to cast [`egui::epaint::Vertex`], [`egui::Vec2`] etc to `&[u8]`. +bytemuck = ["egui/bytemuck"] + +## Enable cut/copy/paste to OS clipboard. +## If disabled a clipboard will be simulated so you can still copy/paste within the egui app. +clipboard = ["arboard", "smithay-clipboard"] + +## Enable opening links in a browser when an egui hyperlink is clicked. +links = ["webbrowser"] + +## Enable profiling with the [`puffin`](https://docs.rs/puffin) crate. +puffin = ["dep:puffin"] + +## Allow serialization of [`WindowSettings`] using [`serde`](https://docs.rs/serde). +serde = ["egui/serde", "dep:serde"] + +## Enables Wayland support. +wayland = ["winit/wayland"] + +[dependencies] +egui = { version = "0.21.0", path = "../egui", default-features = false, features = [ + "tracing", +] } +instant = { version = "0.1", features = [ + "wasm-bindgen", +] } # We use instant so we can (maybe) compile for web +tracing = { version = "0.1", default-features = false, features = ["std"] } +winit = { version = "0.28", default-features = false } + +#! ### Optional dependencies + +# feature accesskit +accesskit_winit = { version = "0.10.0", optional = true } + +## Enable this when generating docs. +document-features = { version = "0.2", optional = true } + +puffin = { version = "0.14", optional = true } +serde = { version = "1.0", optional = true, features = ["derive"] } + +webbrowser = { version = "0.8.3", optional = true } + +[target.'cfg(any(target_os="linux", target_os="dragonfly", target_os="freebsd", target_os="netbsd", target_os="openbsd"))'.dependencies] +smithay-clipboard = { version = "0.6.3", optional = true } + +[target.'cfg(not(target_os = "android"))'.dependencies] +arboard = { version = "3.2", optional = true, default-features = false } + +[target.'cfg(target_os = "android")'.dependencies] +# TODO(emilk): this is probably not the right place for specifying native-activity, but we need to do it somewhere for the CI +android-activity = { version = "0.4", features = ["native-activity"] } diff --git a/nevmes-gui/crates/egui-winit/README.md b/nevmes-gui/crates/egui-winit/README.md new file mode 100644 index 0000000..2386416 --- /dev/null +++ b/nevmes-gui/crates/egui-winit/README.md @@ -0,0 +1,10 @@ +# egui-winit + +[![Latest version](https://img.shields.io/crates/v/egui-winit.svg)](https://crates.io/crates/egui-winit) +[![Documentation](https://docs.rs/egui-winit/badge.svg)](https://docs.rs/egui-winit) +![MIT](https://img.shields.io/badge/license-MIT-blue.svg) +![Apache](https://img.shields.io/badge/license-Apache-blue.svg) + +This crates provides bindings between [`egui`](https://github.com/emilk/egui) and [`winit`](https://crates.io/crates/winit). + +The library translates winit events to egui, handled copy/paste, updates the cursor, open links clicked in egui, etc. diff --git a/nevmes-gui/crates/egui-winit/src/clipboard.rs b/nevmes-gui/crates/egui-winit/src/clipboard.rs new file mode 100644 index 0000000..b2bd630 --- /dev/null +++ b/nevmes-gui/crates/egui-winit/src/clipboard.rs @@ -0,0 +1,146 @@ +use std::os::raw::c_void; + +/// Handles interfacing with the OS clipboard. +/// +/// If the "clipboard" feature is off, or we cannot connect to the OS clipboard, +/// then a fallback clipboard that just works works within the same app is used instead. +pub struct Clipboard { + #[cfg(all(feature = "arboard", not(target_os = "android")))] + arboard: Option, + + #[cfg(all( + any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ), + feature = "smithay-clipboard" + ))] + smithay: Option, + + /// Fallback manual clipboard. + clipboard: String, +} + +impl Clipboard { + #[allow(unused_variables)] + pub fn new(#[allow(unused_variables)] wayland_display: Option<*mut c_void>) -> Self { + Self { + #[cfg(all(feature = "arboard", not(target_os = "android")))] + arboard: init_arboard(), + + #[cfg(all( + any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ), + feature = "smithay-clipboard" + ))] + smithay: init_smithay_clipboard(wayland_display), + + clipboard: Default::default(), + } + } + + pub fn get(&mut self) -> Option { + #[cfg(all( + any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ), + feature = "smithay-clipboard" + ))] + if let Some(clipboard) = &mut self.smithay { + return match clipboard.load() { + Ok(text) => Some(text), + Err(err) => { + tracing::error!("smithay paste error: {err}"); + None + } + }; + } + + #[cfg(all(feature = "arboard", not(target_os = "android")))] + if let Some(clipboard) = &mut self.arboard { + return match clipboard.get_text() { + Ok(text) => Some(text), + Err(err) => { + tracing::error!("arboard paste error: {err}"); + None + } + }; + } + + Some(self.clipboard.clone()) + } + + pub fn set(&mut self, text: String) { + #[cfg(all( + any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ), + feature = "smithay-clipboard" + ))] + if let Some(clipboard) = &mut self.smithay { + clipboard.store(text); + return; + } + + #[cfg(all(feature = "arboard", not(target_os = "android")))] + if let Some(clipboard) = &mut self.arboard { + if let Err(err) = clipboard.set_text(text) { + tracing::error!("arboard copy/cut error: {err}"); + } + return; + } + + self.clipboard = text; + } +} + +#[cfg(all(feature = "arboard", not(target_os = "android")))] +fn init_arboard() -> Option { + tracing::debug!("Initializing arboard clipboard…"); + match arboard::Clipboard::new() { + Ok(clipboard) => Some(clipboard), + Err(err) => { + tracing::warn!("Failed to initialize arboard clipboard: {err}"); + None + } + } +} + +#[cfg(all( + any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ), + feature = "smithay-clipboard" +))] +fn init_smithay_clipboard( + wayland_display: Option<*mut c_void>, +) -> Option { + if let Some(display) = wayland_display { + tracing::debug!("Initializing smithay clipboard…"); + #[allow(unsafe_code)] + Some(unsafe { smithay_clipboard::Clipboard::new(display) }) + } else { + tracing::debug!("Cannot initialize smithay clipboard without a display handle"); + None + } +} diff --git a/nevmes-gui/crates/egui-winit/src/lib.rs b/nevmes-gui/crates/egui-winit/src/lib.rs new file mode 100644 index 0000000..5ae22a4 --- /dev/null +++ b/nevmes-gui/crates/egui-winit/src/lib.rs @@ -0,0 +1,920 @@ +//! [`egui`] bindings for [`winit`](https://github.com/rust-windowing/winit). +//! +//! The library translates winit events to egui, handled copy/paste, +//! updates the cursor, open links clicked in egui, etc. +//! +//! ## Feature flags +#![cfg_attr(feature = "document-features", doc = document_features::document_features!())] +//! + +#![allow(clippy::manual_range_contains)] + +use std::os::raw::c_void; + +#[cfg(feature = "accesskit")] +pub use accesskit_winit; +pub use egui; +#[cfg(feature = "accesskit")] +use egui::accesskit; +pub use winit; + +pub mod clipboard; +mod window_settings; + +pub use window_settings::WindowSettings; + +use winit::event_loop::EventLoopWindowTarget; + +pub fn native_pixels_per_point(window: &winit::window::Window) -> f32 { + window.scale_factor() as f32 +} + +pub fn screen_size_in_pixels(window: &winit::window::Window) -> egui::Vec2 { + let size = window.inner_size(); + egui::vec2(size.width as f32, size.height as f32) +} + +// ---------------------------------------------------------------------------- + +#[must_use] +pub struct EventResponse { + /// If true, egui consumed this event, i.e. wants exclusive use of this event + /// (e.g. a mouse click on an egui window, or entering text into a text field). + /// + /// For instance, if you use egui for a game, you should only + /// pass on the events to your game when [`Self::consumed`] is `false. + /// + /// Note that egui uses `tab` to move focus between elements, so this will always be `true` for tabs. + pub consumed: bool, + + /// Do we need an egui refresh because of this event? + pub repaint: bool, +} + +// ---------------------------------------------------------------------------- + +/// Handles the integration between egui and winit. +pub struct State { + start_time: instant::Instant, + egui_input: egui::RawInput, + pointer_pos_in_points: Option, + any_pointer_button_down: bool, + current_cursor_icon: Option, + + /// What egui uses. + current_pixels_per_point: f32, + + clipboard: clipboard::Clipboard, + + /// If `true`, mouse inputs will be treated as touches. + /// Useful for debugging touch support in egui. + /// + /// Creates duplicate touches, if real touch inputs are coming. + simulate_touch_screen: bool, + + /// Is Some(…) when a touch is being translated to a pointer. + /// + /// Only one touch will be interpreted as pointer at any time. + pointer_touch_id: Option, + + /// track ime state + input_method_editor_started: bool, + + #[cfg(feature = "accesskit")] + accesskit: Option, +} + +impl State { + pub fn new(event_loop: &EventLoopWindowTarget) -> Self { + Self::new_with_wayland_display(wayland_display(event_loop)) + } + + pub fn new_with_wayland_display(wayland_display: Option<*mut c_void>) -> Self { + let egui_input = egui::RawInput { + has_focus: false, // winit will tell us when we have focus + ..Default::default() + }; + + Self { + start_time: instant::Instant::now(), + egui_input, + pointer_pos_in_points: None, + any_pointer_button_down: false, + current_cursor_icon: None, + current_pixels_per_point: 1.0, + + clipboard: clipboard::Clipboard::new(wayland_display), + + simulate_touch_screen: false, + pointer_touch_id: None, + + input_method_editor_started: false, + + #[cfg(feature = "accesskit")] + accesskit: None, + } + } + + #[cfg(feature = "accesskit")] + pub fn init_accesskit + Send>( + &mut self, + window: &winit::window::Window, + event_loop_proxy: winit::event_loop::EventLoopProxy, + initial_tree_update_factory: impl 'static + FnOnce() -> accesskit::TreeUpdate + Send, + ) { + self.accesskit = Some(accesskit_winit::Adapter::new( + window, + initial_tree_update_factory, + event_loop_proxy, + )); + } + + /// Call this once a graphics context has been created to update the maximum texture dimensions + /// that egui will use. + pub fn set_max_texture_side(&mut self, max_texture_side: usize) { + self.egui_input.max_texture_side = Some(max_texture_side); + } + + /// Call this when a new native Window is created for rendering to initialize the `pixels_per_point` + /// for that window. + /// + /// In particular, on Android it is necessary to call this after each `Resumed` lifecycle + /// event, each time a new native window is created. + /// + /// Once this has been initialized for a new window then this state will be maintained by handling + /// [`winit::event::WindowEvent::ScaleFactorChanged`] events. + pub fn set_pixels_per_point(&mut self, pixels_per_point: f32) { + self.egui_input.pixels_per_point = Some(pixels_per_point); + self.current_pixels_per_point = pixels_per_point; + } + + /// The number of physical pixels per logical point, + /// as configured on the current egui context (see [`egui::Context::pixels_per_point`]). + #[inline] + pub fn pixels_per_point(&self) -> f32 { + self.current_pixels_per_point + } + + /// The current input state. + /// This is changed by [`Self::on_event`] and cleared by [`Self::take_egui_input`]. + #[inline] + pub fn egui_input(&self) -> &egui::RawInput { + &self.egui_input + } + + /// Prepare for a new frame by extracting the accumulated input, + /// as well as setting [the time](egui::RawInput::time) and [screen rectangle](egui::RawInput::screen_rect). + pub fn take_egui_input(&mut self, window: &winit::window::Window) -> egui::RawInput { + let pixels_per_point = self.pixels_per_point(); + + self.egui_input.time = Some(self.start_time.elapsed().as_secs_f64()); + + // On Windows, a minimized window will have 0 width and height. + // See: https://github.com/rust-windowing/winit/issues/208 + // This solves an issue where egui window positions would be changed when minimizing on Windows. + let screen_size_in_pixels = screen_size_in_pixels(window); + let screen_size_in_points = screen_size_in_pixels / pixels_per_point; + self.egui_input.screen_rect = + if screen_size_in_points.x > 0.0 && screen_size_in_points.y > 0.0 { + Some(egui::Rect::from_min_size( + egui::Pos2::ZERO, + screen_size_in_points, + )) + } else { + None + }; + + self.egui_input.take() + } + + /// Call this when there is a new event. + /// + /// The result can be found in [`Self::egui_input`] and be extracted with [`Self::take_egui_input`]. + pub fn on_event( + &mut self, + egui_ctx: &egui::Context, + event: &winit::event::WindowEvent<'_>, + ) -> EventResponse { + use winit::event::WindowEvent; + match event { + WindowEvent::ScaleFactorChanged { scale_factor, .. } => { + let pixels_per_point = *scale_factor as f32; + self.egui_input.pixels_per_point = Some(pixels_per_point); + self.current_pixels_per_point = pixels_per_point; + EventResponse { + repaint: true, + consumed: false, + } + } + WindowEvent::MouseInput { state, button, .. } => { + self.on_mouse_button_input(*state, *button); + EventResponse { + repaint: true, + consumed: egui_ctx.wants_pointer_input(), + } + } + WindowEvent::MouseWheel { delta, .. } => { + self.on_mouse_wheel(*delta); + EventResponse { + repaint: true, + consumed: egui_ctx.wants_pointer_input(), + } + } + WindowEvent::CursorMoved { position, .. } => { + self.on_cursor_moved(*position); + EventResponse { + repaint: true, + consumed: egui_ctx.is_using_pointer(), + } + } + WindowEvent::CursorLeft { .. } => { + self.pointer_pos_in_points = None; + self.egui_input.events.push(egui::Event::PointerGone); + EventResponse { + repaint: true, + consumed: false, + } + } + // WindowEvent::TouchpadPressure {device_id, pressure, stage, .. } => {} // TODO + WindowEvent::Touch(touch) => { + self.on_touch(touch); + let consumed = match touch.phase { + winit::event::TouchPhase::Started + | winit::event::TouchPhase::Ended + | winit::event::TouchPhase::Cancelled => egui_ctx.wants_pointer_input(), + winit::event::TouchPhase::Moved => egui_ctx.is_using_pointer(), + }; + EventResponse { + repaint: true, + consumed, + } + } + WindowEvent::ReceivedCharacter(ch) => { + // On Mac we get here when the user presses Cmd-C (copy), ctrl-W, etc. + // We need to ignore these characters that are side-effects of commands. + let is_mac_cmd = cfg!(target_os = "macos") + && (self.egui_input.modifiers.ctrl || self.egui_input.modifiers.mac_cmd); + + let consumed = if is_printable_char(*ch) && !is_mac_cmd { + self.egui_input + .events + .push(egui::Event::Text(ch.to_string())); + egui_ctx.wants_keyboard_input() + } else { + false + }; + EventResponse { + repaint: true, + consumed, + } + } + WindowEvent::Ime(ime) => { + // on Mac even Cmd-C is preessed during ime, a `c` is pushed to Preedit. + // So no need to check is_mac_cmd. + // + // How winit produce `Ime::Enabled` and `Ime::Disabled` differs in MacOS + // and Windows. + // + // - On Windows, before and after each Commit will produce an Enable/Disabled + // event. + // - On MacOS, only when user explicit enable/disable ime. No Disabled + // after Commit. + // + // We use input_method_editor_started to mannualy insert CompositionStart + // between Commits. + match ime { + winit::event::Ime::Enabled | winit::event::Ime::Disabled => (), + winit::event::Ime::Commit(text) => { + self.input_method_editor_started = false; + self.egui_input + .events + .push(egui::Event::CompositionEnd(text.clone())); + } + winit::event::Ime::Preedit(text, ..) => { + if !self.input_method_editor_started { + self.input_method_editor_started = true; + self.egui_input.events.push(egui::Event::CompositionStart); + } + self.egui_input + .events + .push(egui::Event::CompositionUpdate(text.clone())); + } + }; + + EventResponse { + repaint: true, + consumed: egui_ctx.wants_keyboard_input(), + } + } + WindowEvent::KeyboardInput { input, .. } => { + self.on_keyboard_input(input); + let consumed = egui_ctx.wants_keyboard_input() + || input.virtual_keycode == Some(winit::event::VirtualKeyCode::Tab); + EventResponse { + repaint: true, + consumed, + } + } + WindowEvent::Focused(has_focus) => { + self.egui_input.has_focus = *has_focus; + // We will not be given a KeyboardInput event when the modifiers are released while + // the window does not have focus. Unset all modifier state to be safe. + self.egui_input.modifiers = egui::Modifiers::default(); + EventResponse { + repaint: true, + consumed: false, + } + } + WindowEvent::HoveredFile(path) => { + self.egui_input.hovered_files.push(egui::HoveredFile { + path: Some(path.clone()), + ..Default::default() + }); + EventResponse { + repaint: true, + consumed: false, + } + } + WindowEvent::HoveredFileCancelled => { + self.egui_input.hovered_files.clear(); + EventResponse { + repaint: true, + consumed: false, + } + } + WindowEvent::DroppedFile(path) => { + self.egui_input.hovered_files.clear(); + self.egui_input.dropped_files.push(egui::DroppedFile { + path: Some(path.clone()), + ..Default::default() + }); + EventResponse { + repaint: true, + consumed: false, + } + } + WindowEvent::ModifiersChanged(state) => { + self.egui_input.modifiers.alt = state.alt(); + self.egui_input.modifiers.ctrl = state.ctrl(); + self.egui_input.modifiers.shift = state.shift(); + self.egui_input.modifiers.mac_cmd = cfg!(target_os = "macos") && state.logo(); + self.egui_input.modifiers.command = if cfg!(target_os = "macos") { + state.logo() + } else { + state.ctrl() + }; + EventResponse { + repaint: true, + consumed: false, + } + } + + // Things that may require repaint: + WindowEvent::CloseRequested + | WindowEvent::CursorEntered { .. } + | WindowEvent::Destroyed + | WindowEvent::Occluded(_) + | WindowEvent::Resized(_) + | WindowEvent::ThemeChanged(_) + | WindowEvent::TouchpadPressure { .. } => EventResponse { + repaint: true, + consumed: false, + }, + + // Things we completely ignore: + WindowEvent::AxisMotion { .. } + | WindowEvent::Moved(_) + | WindowEvent::SmartMagnify { .. } + | WindowEvent::TouchpadRotate { .. } => EventResponse { + repaint: false, + consumed: false, + }, + + WindowEvent::TouchpadMagnify { delta, .. } => { + // Positive delta values indicate magnification (zooming in). + // Negative delta values indicate shrinking (zooming out). + let zoom_factor = (*delta as f32).exp(); + self.egui_input.events.push(egui::Event::Zoom(zoom_factor)); + EventResponse { + repaint: true, + consumed: egui_ctx.wants_pointer_input(), + } + } + } + } + + /// Call this when there is a new [`accesskit::ActionRequest`]. + /// + /// The result can be found in [`Self::egui_input`] and be extracted with [`Self::take_egui_input`]. + #[cfg(feature = "accesskit")] + pub fn on_accesskit_action_request(&mut self, request: accesskit::ActionRequest) { + self.egui_input + .events + .push(egui::Event::AccessKitActionRequest(request)); + } + + fn on_mouse_button_input( + &mut self, + state: winit::event::ElementState, + button: winit::event::MouseButton, + ) { + if let Some(pos) = self.pointer_pos_in_points { + if let Some(button) = translate_mouse_button(button) { + let pressed = state == winit::event::ElementState::Pressed; + + self.egui_input.events.push(egui::Event::PointerButton { + pos, + button, + pressed, + modifiers: self.egui_input.modifiers, + }); + + if self.simulate_touch_screen { + if pressed { + self.any_pointer_button_down = true; + + self.egui_input.events.push(egui::Event::Touch { + device_id: egui::TouchDeviceId(0), + id: egui::TouchId(0), + phase: egui::TouchPhase::Start, + pos, + force: 0.0, + }); + } else { + self.any_pointer_button_down = false; + + self.egui_input.events.push(egui::Event::PointerGone); + + self.egui_input.events.push(egui::Event::Touch { + device_id: egui::TouchDeviceId(0), + id: egui::TouchId(0), + phase: egui::TouchPhase::End, + pos, + force: 0.0, + }); + }; + } + } + } + } + + fn on_cursor_moved(&mut self, pos_in_pixels: winit::dpi::PhysicalPosition) { + let pos_in_points = egui::pos2( + pos_in_pixels.x as f32 / self.pixels_per_point(), + pos_in_pixels.y as f32 / self.pixels_per_point(), + ); + self.pointer_pos_in_points = Some(pos_in_points); + + if self.simulate_touch_screen { + if self.any_pointer_button_down { + self.egui_input + .events + .push(egui::Event::PointerMoved(pos_in_points)); + + self.egui_input.events.push(egui::Event::Touch { + device_id: egui::TouchDeviceId(0), + id: egui::TouchId(0), + phase: egui::TouchPhase::Move, + pos: pos_in_points, + force: 0.0, + }); + } + } else { + self.egui_input + .events + .push(egui::Event::PointerMoved(pos_in_points)); + } + } + + fn on_touch(&mut self, touch: &winit::event::Touch) { + // Emit touch event + self.egui_input.events.push(egui::Event::Touch { + device_id: egui::TouchDeviceId(egui::epaint::util::hash(touch.device_id)), + id: egui::TouchId::from(touch.id), + phase: match touch.phase { + winit::event::TouchPhase::Started => egui::TouchPhase::Start, + winit::event::TouchPhase::Moved => egui::TouchPhase::Move, + winit::event::TouchPhase::Ended => egui::TouchPhase::End, + winit::event::TouchPhase::Cancelled => egui::TouchPhase::Cancel, + }, + pos: egui::pos2( + touch.location.x as f32 / self.pixels_per_point(), + touch.location.y as f32 / self.pixels_per_point(), + ), + force: match touch.force { + Some(winit::event::Force::Normalized(force)) => force as f32, + Some(winit::event::Force::Calibrated { + force, + max_possible_force, + .. + }) => (force / max_possible_force) as f32, + None => 0_f32, + }, + }); + // If we're not yet tanslating a touch or we're translating this very + // touch … + if self.pointer_touch_id.is_none() || self.pointer_touch_id.unwrap() == touch.id { + // … emit PointerButton resp. PointerMoved events to emulate mouse + match touch.phase { + winit::event::TouchPhase::Started => { + self.pointer_touch_id = Some(touch.id); + // First move the pointer to the right location + self.on_cursor_moved(touch.location); + self.on_mouse_button_input( + winit::event::ElementState::Pressed, + winit::event::MouseButton::Left, + ); + } + winit::event::TouchPhase::Moved => { + self.on_cursor_moved(touch.location); + } + winit::event::TouchPhase::Ended => { + self.pointer_touch_id = None; + self.on_mouse_button_input( + winit::event::ElementState::Released, + winit::event::MouseButton::Left, + ); + // The pointer should vanish completely to not get any + // hover effects + self.pointer_pos_in_points = None; + self.egui_input.events.push(egui::Event::PointerGone); + } + winit::event::TouchPhase::Cancelled => { + self.pointer_touch_id = None; + self.pointer_pos_in_points = None; + self.egui_input.events.push(egui::Event::PointerGone); + } + } + } + } + + fn on_mouse_wheel(&mut self, delta: winit::event::MouseScrollDelta) { + let delta = match delta { + winit::event::MouseScrollDelta::LineDelta(x, y) => { + let points_per_scroll_line = 50.0; // Scroll speed decided by consensus: https://github.com/emilk/egui/issues/461 + egui::vec2(x, y) * points_per_scroll_line + } + winit::event::MouseScrollDelta::PixelDelta(delta) => { + egui::vec2(delta.x as f32, delta.y as f32) / self.pixels_per_point() + } + }; + + if self.egui_input.modifiers.ctrl || self.egui_input.modifiers.command { + // Treat as zoom instead: + let factor = (delta.y / 200.0).exp(); + self.egui_input.events.push(egui::Event::Zoom(factor)); + } else if self.egui_input.modifiers.shift { + // Treat as horizontal scrolling. + // Note: one Mac we already get horizontal scroll events when shift is down. + self.egui_input + .events + .push(egui::Event::Scroll(egui::vec2(delta.x + delta.y, 0.0))); + } else { + self.egui_input.events.push(egui::Event::Scroll(delta)); + } + } + + fn on_keyboard_input(&mut self, input: &winit::event::KeyboardInput) { + if let Some(keycode) = input.virtual_keycode { + let pressed = input.state == winit::event::ElementState::Pressed; + + if pressed { + // VirtualKeyCode::Paste etc in winit are broken/untrustworthy, + // so we detect these things manually: + if is_cut_command(self.egui_input.modifiers, keycode) { + self.egui_input.events.push(egui::Event::Cut); + } else if is_copy_command(self.egui_input.modifiers, keycode) { + self.egui_input.events.push(egui::Event::Copy); + } else if is_paste_command(self.egui_input.modifiers, keycode) { + if let Some(contents) = self.clipboard.get() { + let contents = contents.replace("\r\n", "\n"); + if !contents.is_empty() { + self.egui_input.events.push(egui::Event::Paste(contents)); + } + } + } + } + + if let Some(key) = translate_virtual_key_code(keycode) { + self.egui_input.events.push(egui::Event::Key { + key, + pressed, + repeat: false, // egui will fill this in for us! + modifiers: self.egui_input.modifiers, + }); + } + } + } + + /// Call with the output given by `egui`. + /// + /// This will, if needed: + /// * update the cursor + /// * copy text to the clipboard + /// * open any clicked urls + /// * update the IME + /// * + pub fn handle_platform_output( + &mut self, + window: &winit::window::Window, + egui_ctx: &egui::Context, + platform_output: egui::PlatformOutput, + ) { + let egui::PlatformOutput { + cursor_icon, + open_url, + copied_text, + events: _, // handled above + mutable_text_under_cursor: _, // only used in eframe web + text_cursor_pos, + #[cfg(feature = "accesskit")] + accesskit_update, + } = platform_output; + self.current_pixels_per_point = egui_ctx.pixels_per_point(); // someone can have changed it to scale the UI + + self.set_cursor_icon(window, cursor_icon); + + if let Some(open_url) = open_url { + open_url_in_browser(&open_url.url); + } + + if !copied_text.is_empty() { + self.clipboard.set(copied_text); + } + + if let Some(egui::Pos2 { x, y }) = text_cursor_pos { + window.set_ime_position(winit::dpi::LogicalPosition { x, y }); + } + + #[cfg(feature = "accesskit")] + if let Some(accesskit) = self.accesskit.as_ref() { + if let Some(update) = accesskit_update { + accesskit.update_if_active(|| update); + } + } + } + + fn set_cursor_icon(&mut self, window: &winit::window::Window, cursor_icon: egui::CursorIcon) { + if self.current_cursor_icon == Some(cursor_icon) { + // Prevent flickering near frame boundary when Windows OS tries to control cursor icon for window resizing. + // On other platforms: just early-out to save CPU. + return; + } + + let is_pointer_in_window = self.pointer_pos_in_points.is_some(); + if is_pointer_in_window { + self.current_cursor_icon = Some(cursor_icon); + + if let Some(winit_cursor_icon) = translate_cursor(cursor_icon) { + window.set_cursor_visible(true); + window.set_cursor_icon(winit_cursor_icon); + } else { + window.set_cursor_visible(false); + } + } else { + // Remember to set the cursor again once the cursor returns to the screen: + self.current_cursor_icon = None; + } + } +} + +fn open_url_in_browser(_url: &str) { + #[cfg(feature = "webbrowser")] + if let Err(err) = webbrowser::open(_url) { + tracing::warn!("Failed to open url: {}", err); + } + + #[cfg(not(feature = "webbrowser"))] + { + tracing::warn!("Cannot open url - feature \"links\" not enabled."); + } +} + +/// Winit sends special keys (backspace, delete, F1, …) as characters. +/// Ignore those. +/// We also ignore '\r', '\n', '\t'. +/// Newlines are handled by the `Key::Enter` event. +fn is_printable_char(chr: char) -> bool { + let is_in_private_use_area = '\u{e000}' <= chr && chr <= '\u{f8ff}' + || '\u{f0000}' <= chr && chr <= '\u{ffffd}' + || '\u{100000}' <= chr && chr <= '\u{10fffd}'; + + !is_in_private_use_area && !chr.is_ascii_control() +} + +fn is_cut_command(modifiers: egui::Modifiers, keycode: winit::event::VirtualKeyCode) -> bool { + (modifiers.command && keycode == winit::event::VirtualKeyCode::X) + || (cfg!(target_os = "windows") + && modifiers.shift + && keycode == winit::event::VirtualKeyCode::Delete) +} + +fn is_copy_command(modifiers: egui::Modifiers, keycode: winit::event::VirtualKeyCode) -> bool { + (modifiers.command && keycode == winit::event::VirtualKeyCode::C) + || (cfg!(target_os = "windows") + && modifiers.ctrl + && keycode == winit::event::VirtualKeyCode::Insert) +} + +fn is_paste_command(modifiers: egui::Modifiers, keycode: winit::event::VirtualKeyCode) -> bool { + (modifiers.command && keycode == winit::event::VirtualKeyCode::V) + || (cfg!(target_os = "windows") + && modifiers.shift + && keycode == winit::event::VirtualKeyCode::Insert) +} + +fn translate_mouse_button(button: winit::event::MouseButton) -> Option { + match button { + winit::event::MouseButton::Left => Some(egui::PointerButton::Primary), + winit::event::MouseButton::Right => Some(egui::PointerButton::Secondary), + winit::event::MouseButton::Middle => Some(egui::PointerButton::Middle), + winit::event::MouseButton::Other(1) => Some(egui::PointerButton::Extra1), + winit::event::MouseButton::Other(2) => Some(egui::PointerButton::Extra2), + winit::event::MouseButton::Other(_) => None, + } +} + +fn translate_virtual_key_code(key: winit::event::VirtualKeyCode) -> Option { + use egui::Key; + use winit::event::VirtualKeyCode; + + Some(match key { + VirtualKeyCode::Down => Key::ArrowDown, + VirtualKeyCode::Left => Key::ArrowLeft, + VirtualKeyCode::Right => Key::ArrowRight, + VirtualKeyCode::Up => Key::ArrowUp, + + VirtualKeyCode::Escape => Key::Escape, + VirtualKeyCode::Tab => Key::Tab, + VirtualKeyCode::Back => Key::Backspace, + VirtualKeyCode::Return => Key::Enter, + VirtualKeyCode::Space => Key::Space, + + VirtualKeyCode::Insert => Key::Insert, + VirtualKeyCode::Delete => Key::Delete, + VirtualKeyCode::Home => Key::Home, + VirtualKeyCode::End => Key::End, + VirtualKeyCode::PageUp => Key::PageUp, + VirtualKeyCode::PageDown => Key::PageDown, + + VirtualKeyCode::Minus => Key::Minus, + // Using Mac the key with the Plus sign on it is reported as the Equals key + // (with both English and Swedish keyboard). + VirtualKeyCode::Equals => Key::PlusEquals, + + VirtualKeyCode::Key0 | VirtualKeyCode::Numpad0 => Key::Num0, + VirtualKeyCode::Key1 | VirtualKeyCode::Numpad1 => Key::Num1, + VirtualKeyCode::Key2 | VirtualKeyCode::Numpad2 => Key::Num2, + VirtualKeyCode::Key3 | VirtualKeyCode::Numpad3 => Key::Num3, + VirtualKeyCode::Key4 | VirtualKeyCode::Numpad4 => Key::Num4, + VirtualKeyCode::Key5 | VirtualKeyCode::Numpad5 => Key::Num5, + VirtualKeyCode::Key6 | VirtualKeyCode::Numpad6 => Key::Num6, + VirtualKeyCode::Key7 | VirtualKeyCode::Numpad7 => Key::Num7, + VirtualKeyCode::Key8 | VirtualKeyCode::Numpad8 => Key::Num8, + VirtualKeyCode::Key9 | VirtualKeyCode::Numpad9 => Key::Num9, + + VirtualKeyCode::A => Key::A, + VirtualKeyCode::B => Key::B, + VirtualKeyCode::C => Key::C, + VirtualKeyCode::D => Key::D, + VirtualKeyCode::E => Key::E, + VirtualKeyCode::F => Key::F, + VirtualKeyCode::G => Key::G, + VirtualKeyCode::H => Key::H, + VirtualKeyCode::I => Key::I, + VirtualKeyCode::J => Key::J, + VirtualKeyCode::K => Key::K, + VirtualKeyCode::L => Key::L, + VirtualKeyCode::M => Key::M, + VirtualKeyCode::N => Key::N, + VirtualKeyCode::O => Key::O, + VirtualKeyCode::P => Key::P, + VirtualKeyCode::Q => Key::Q, + VirtualKeyCode::R => Key::R, + VirtualKeyCode::S => Key::S, + VirtualKeyCode::T => Key::T, + VirtualKeyCode::U => Key::U, + VirtualKeyCode::V => Key::V, + VirtualKeyCode::W => Key::W, + VirtualKeyCode::X => Key::X, + VirtualKeyCode::Y => Key::Y, + VirtualKeyCode::Z => Key::Z, + + VirtualKeyCode::F1 => Key::F1, + VirtualKeyCode::F2 => Key::F2, + VirtualKeyCode::F3 => Key::F3, + VirtualKeyCode::F4 => Key::F4, + VirtualKeyCode::F5 => Key::F5, + VirtualKeyCode::F6 => Key::F6, + VirtualKeyCode::F7 => Key::F7, + VirtualKeyCode::F8 => Key::F8, + VirtualKeyCode::F9 => Key::F9, + VirtualKeyCode::F10 => Key::F10, + VirtualKeyCode::F11 => Key::F11, + VirtualKeyCode::F12 => Key::F12, + VirtualKeyCode::F13 => Key::F13, + VirtualKeyCode::F14 => Key::F14, + VirtualKeyCode::F15 => Key::F15, + VirtualKeyCode::F16 => Key::F16, + VirtualKeyCode::F17 => Key::F17, + VirtualKeyCode::F18 => Key::F18, + VirtualKeyCode::F19 => Key::F19, + VirtualKeyCode::F20 => Key::F20, + + _ => { + return None; + } + }) +} + +fn translate_cursor(cursor_icon: egui::CursorIcon) -> Option { + match cursor_icon { + egui::CursorIcon::None => None, + + egui::CursorIcon::Alias => Some(winit::window::CursorIcon::Alias), + egui::CursorIcon::AllScroll => Some(winit::window::CursorIcon::AllScroll), + egui::CursorIcon::Cell => Some(winit::window::CursorIcon::Cell), + egui::CursorIcon::ContextMenu => Some(winit::window::CursorIcon::ContextMenu), + egui::CursorIcon::Copy => Some(winit::window::CursorIcon::Copy), + egui::CursorIcon::Crosshair => Some(winit::window::CursorIcon::Crosshair), + egui::CursorIcon::Default => Some(winit::window::CursorIcon::Default), + egui::CursorIcon::Grab => Some(winit::window::CursorIcon::Grab), + egui::CursorIcon::Grabbing => Some(winit::window::CursorIcon::Grabbing), + egui::CursorIcon::Help => Some(winit::window::CursorIcon::Help), + egui::CursorIcon::Move => Some(winit::window::CursorIcon::Move), + egui::CursorIcon::NoDrop => Some(winit::window::CursorIcon::NoDrop), + egui::CursorIcon::NotAllowed => Some(winit::window::CursorIcon::NotAllowed), + egui::CursorIcon::PointingHand => Some(winit::window::CursorIcon::Hand), + egui::CursorIcon::Progress => Some(winit::window::CursorIcon::Progress), + + egui::CursorIcon::ResizeHorizontal => Some(winit::window::CursorIcon::EwResize), + egui::CursorIcon::ResizeNeSw => Some(winit::window::CursorIcon::NeswResize), + egui::CursorIcon::ResizeNwSe => Some(winit::window::CursorIcon::NwseResize), + egui::CursorIcon::ResizeVertical => Some(winit::window::CursorIcon::NsResize), + + egui::CursorIcon::ResizeEast => Some(winit::window::CursorIcon::EResize), + egui::CursorIcon::ResizeSouthEast => Some(winit::window::CursorIcon::SeResize), + egui::CursorIcon::ResizeSouth => Some(winit::window::CursorIcon::SResize), + egui::CursorIcon::ResizeSouthWest => Some(winit::window::CursorIcon::SwResize), + egui::CursorIcon::ResizeWest => Some(winit::window::CursorIcon::WResize), + egui::CursorIcon::ResizeNorthWest => Some(winit::window::CursorIcon::NwResize), + egui::CursorIcon::ResizeNorth => Some(winit::window::CursorIcon::NResize), + egui::CursorIcon::ResizeNorthEast => Some(winit::window::CursorIcon::NeResize), + egui::CursorIcon::ResizeColumn => Some(winit::window::CursorIcon::ColResize), + egui::CursorIcon::ResizeRow => Some(winit::window::CursorIcon::RowResize), + + egui::CursorIcon::Text => Some(winit::window::CursorIcon::Text), + egui::CursorIcon::VerticalText => Some(winit::window::CursorIcon::VerticalText), + egui::CursorIcon::Wait => Some(winit::window::CursorIcon::Wait), + egui::CursorIcon::ZoomIn => Some(winit::window::CursorIcon::ZoomIn), + egui::CursorIcon::ZoomOut => Some(winit::window::CursorIcon::ZoomOut), + } +} + +/// Returns a Wayland display handle if the target is running Wayland +fn wayland_display(_event_loop: &EventLoopWindowTarget) -> Option<*mut c_void> { + #[cfg(feature = "wayland")] + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + { + use winit::platform::wayland::EventLoopWindowTargetExtWayland as _; + return _event_loop.wayland_display(); + } + + #[allow(unreachable_code)] + { + let _ = _event_loop; + None + } +} + +// --------------------------------------------------------------------------- + +/// Profiling macro for feature "puffin" +#[allow(unused_macros)] +macro_rules! profile_function { + ($($arg: tt)*) => { + #[cfg(feature = "puffin")] + puffin::profile_function!($($arg)*); + }; +} + +#[allow(unused_imports)] +pub(crate) use profile_function; + +/// Profiling macro for feature "puffin" +#[allow(unused_macros)] +macro_rules! profile_scope { + ($($arg: tt)*) => { + #[cfg(feature = "puffin")] + puffin::profile_scope!($($arg)*); + }; +} + +#[allow(unused_imports)] +pub(crate) use profile_scope; diff --git a/nevmes-gui/crates/egui-winit/src/window_settings.rs b/nevmes-gui/crates/egui-winit/src/window_settings.rs new file mode 100644 index 0000000..0137af2 --- /dev/null +++ b/nevmes-gui/crates/egui-winit/src/window_settings.rs @@ -0,0 +1,144 @@ +/// Can be used to store native window settings (position and size). +#[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub struct WindowSettings { + /// Position of window in physical pixels. This is either + /// the inner or outer position depending on the platform. + /// See [`winit::window::WindowBuilder::with_position`] for details. + position: Option, + + fullscreen: bool, + + /// Inner size of window in logical pixels + inner_size_points: Option, +} + +impl WindowSettings { + pub fn from_display(window: &winit::window::Window) -> Self { + let inner_size_points = window.inner_size().to_logical::(window.scale_factor()); + let position = if cfg!(macos) { + // MacOS uses inner position when positioning windows. + window + .inner_position() + .ok() + .map(|p| egui::pos2(p.x as f32, p.y as f32)) + } else { + // Other platforms use the outer position. + window + .outer_position() + .ok() + .map(|p| egui::pos2(p.x as f32, p.y as f32)) + }; + + Self { + position, + + fullscreen: window.fullscreen().is_some(), + + inner_size_points: Some(egui::vec2( + inner_size_points.width, + inner_size_points.height, + )), + } + } + + pub fn inner_size_points(&self) -> Option { + self.inner_size_points + } + + pub fn initialize_window( + &self, + mut window: winit::window::WindowBuilder, + ) -> winit::window::WindowBuilder { + // If the app last ran on two monitors and only one is now connected, then + // the given position is invalid. + // If this happens on Mac, the window is clamped into valid area. + // If this happens on Windows, the clamping behavior is managed by the function + // clamp_window_to_sane_position. + if let Some(pos) = self.position { + window = window.with_position(winit::dpi::PhysicalPosition { + x: pos.x as f64, + y: pos.y as f64, + }); + } + + if let Some(inner_size_points) = self.inner_size_points { + window + .with_inner_size(winit::dpi::LogicalSize { + width: inner_size_points.x as f64, + height: inner_size_points.y as f64, + }) + .with_fullscreen( + self.fullscreen + .then_some(winit::window::Fullscreen::Borderless(None)), + ) + } else { + window + } + } + + pub fn clamp_to_sane_values(&mut self, max_size: egui::Vec2) { + use egui::NumExt as _; + + if let Some(size) = &mut self.inner_size_points { + // Prevent ridiculously small windows + let min_size = egui::Vec2::splat(64.0); + *size = size.at_least(min_size); + *size = size.at_most(max_size); + } + } + + pub fn clamp_window_to_sane_position( + &mut self, + event_loop: &winit::event_loop::EventLoopWindowTarget, + ) { + if let (Some(position), Some(inner_size_points)) = + (&mut self.position, &self.inner_size_points) + { + let monitors = event_loop.available_monitors(); + // default to primary monitor, in case the correct monitor was disconnected. + let mut active_monitor = if let Some(active_monitor) = event_loop + .primary_monitor() + .or_else(|| event_loop.available_monitors().next()) + { + active_monitor + } else { + return; // no monitors 🤷 + }; + for monitor in monitors { + let monitor_x_range = (monitor.position().x - inner_size_points.x as i32) + ..(monitor.position().x + monitor.size().width as i32); + let monitor_y_range = (monitor.position().y - inner_size_points.y as i32) + ..(monitor.position().y + monitor.size().height as i32); + + if monitor_x_range.contains(&(position.x as i32)) + && monitor_y_range.contains(&(position.y as i32)) + { + active_monitor = monitor; + } + } + + let mut inner_size_pixels = *inner_size_points * (active_monitor.scale_factor() as f32); + // Add size of title bar. This is 32 px by default in Win 10/11. + if cfg!(target_os = "windows") { + inner_size_pixels += + egui::Vec2::new(0.0, 32.0 * active_monitor.scale_factor() as f32); + } + let monitor_position = egui::Pos2::new( + active_monitor.position().x as f32, + active_monitor.position().y as f32, + ); + let monitor_size = egui::Vec2::new( + active_monitor.size().width as f32, + active_monitor.size().height as f32, + ); + + // Window size cannot be negative or the subsequent `clamp` will panic. + let window_size = (monitor_size - inner_size_pixels).max(egui::Vec2::ZERO); + // To get the maximum position, we get the rightmost corner of the display, then + // subtract the size of the window to get the bottom right most value window.position + // can have. + *position = position.clamp(monitor_position, monitor_position + window_size); + } + } +} diff --git a/nevmes-gui/crates/egui/Cargo.toml b/nevmes-gui/crates/egui/Cargo.toml new file mode 100644 index 0000000..433da94 --- /dev/null +++ b/nevmes-gui/crates/egui/Cargo.toml @@ -0,0 +1,84 @@ +[package] +name = "egui" +version = "0.21.0" +authors = ["Emil Ernerfeldt "] +description = "An easy-to-use immediate mode GUI that runs on both web and native" +edition = "2021" +rust-version = "1.65" +homepage = "https://github.com/emilk/egui" +license = "MIT OR Apache-2.0" +readme = "../../README.md" +repository = "https://github.com/emilk/egui" +categories = ["gui", "game-development"] +keywords = ["gui", "imgui", "immediate", "portable", "gamedev"] +include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"] + +[package.metadata.docs.rs] +all-features = true + +[lib] + + +[features] +default = ["default_fonts"] + +## [`bytemuck`](https://docs.rs/bytemuck) enables you to cast [`epaint::Vertex`], [`emath::Vec2`] etc to `&[u8]`. +bytemuck = ["epaint/bytemuck"] + +## [`cint`](https://docs.rs/cint) enables interopability with other color libraries. +cint = ["epaint/cint"] + +## Enable the [`hex_color`] macro. +color-hex = ["epaint/color-hex"] + +## This will automatically detect deadlocks due to double-locking on the same thread. +## If your app freezes, you may want to enable this! +## Only affects [`epaint::mutex::RwLock`] (which egui uses a lot). +deadlock_detection = ["epaint/deadlock_detection"] + +## If set, egui will use `include_bytes!` to bundle some fonts. +## If you plan on specifying your own fonts you may disable this feature. +default_fonts = ["epaint/default_fonts"] + +## Enable additional checks if debug assertions are enabled (debug builds). +extra_debug_asserts = ["epaint/extra_debug_asserts"] +## Always enable additional checks. +extra_asserts = ["epaint/extra_asserts"] + +## [`mint`](https://docs.rs/mint) enables interopability with other math libraries such as [`glam`](https://docs.rs/glam) and [`nalgebra`](https://docs.rs/nalgebra). +mint = ["epaint/mint"] + +## Enable persistence of memory (window positions etc). +persistence = ["serde", "epaint/serde", "ron"] + +## Allow serialization using [`serde`](https://docs.rs/serde). +serde = ["dep:serde", "epaint/serde", "accesskit?/serde"] + +## Change Vertex layout to be compatible with unity +unity = ["epaint/unity"] + + +[dependencies] +epaint = { version = "0.21.0", path = "../epaint", default-features = false } + +ahash = { version = "0.8.1", default-features = false, features = [ + "no-rng", # we don't need DOS-protection, so we let users opt-in to it instead + "std", +] } +nohash-hasher = "0.2" + +#! ### Optional dependencies +## Exposes detailed accessibility implementation required by platform +## accessibility APIs. Also requires support in the egui integration. +accesskit = { version = "0.9.0", optional = true } + +## Enable this when generating docs. +document-features = { version = "0.2", optional = true } + +ron = { version = "0.8", optional = true } +serde = { version = "1", optional = true, features = ["derive", "rc"] } + +# egui doesn't log much, but when it does, it uses [`tracing`](https://docs.rs/tracing). +tracing = { version = "0.1", optional = true, default-features = false, features = [ + "std", +] } diff --git a/nevmes-gui/crates/egui/README.md b/nevmes-gui/crates/egui/README.md new file mode 100644 index 0000000..8abcec6 --- /dev/null +++ b/nevmes-gui/crates/egui/README.md @@ -0,0 +1,2 @@ +# GUI implementation +This is the core library crate egui. It is fully platform independent without any backend. You give the egui library input each frame (mouse pos etc), and it outputs a triangle mesh for you to paint. diff --git a/nevmes-gui/crates/egui/examples/README.md b/nevmes-gui/crates/egui/examples/README.md new file mode 100644 index 0000000..2df28f1 --- /dev/null +++ b/nevmes-gui/crates/egui/examples/README.md @@ -0,0 +1,7 @@ +There are no stand-alone egui examples, because egui is not stand-alone! + +See the top-level [examples](https://github.com/emilk/egui/tree/master/examples/) folder instead. + +There are also plenty of examples in [the online demo](https://www.egui.rs/#demo). You can find the source code for it at . + +To learn how to set up `eframe` for web and native, go to and follow the instructions there! diff --git a/nevmes-gui/crates/egui/src/animation_manager.rs b/nevmes-gui/crates/egui/src/animation_manager.rs new file mode 100644 index 0000000..be18150 --- /dev/null +++ b/nevmes-gui/crates/egui/src/animation_manager.rs @@ -0,0 +1,114 @@ +use crate::{emath::remap_clamp, Id, IdMap, InputState}; + +#[derive(Clone, Default)] +pub(crate) struct AnimationManager { + bools: IdMap, + values: IdMap, +} + +#[derive(Clone, Debug)] +struct BoolAnim { + value: bool, + + /// when did `value` last toggle? + toggle_time: f64, +} + +#[derive(Clone, Debug)] +struct ValueAnim { + from_value: f32, + + to_value: f32, + + /// when did `value` last toggle? + toggle_time: f64, +} + +impl AnimationManager { + /// See `Context::animate_bool` for documentation + pub fn animate_bool( + &mut self, + input: &InputState, + animation_time: f32, + id: Id, + value: bool, + ) -> f32 { + match self.bools.get_mut(&id) { + None => { + self.bools.insert( + id, + BoolAnim { + value, + toggle_time: -f64::INFINITY, // long time ago + }, + ); + if value { + 1.0 + } else { + 0.0 + } + } + Some(anim) => { + if anim.value != value { + anim.value = value; + anim.toggle_time = input.time; + } + + let time_since_toggle = (input.time - anim.toggle_time) as f32; + + // On the frame we toggle we don't want to return the old value, + // so we extrapolate forwards: + let time_since_toggle = time_since_toggle + input.predicted_dt; + + if value { + remap_clamp(time_since_toggle, 0.0..=animation_time, 0.0..=1.0) + } else { + remap_clamp(time_since_toggle, 0.0..=animation_time, 1.0..=0.0) + } + } + } + } + + pub fn animate_value( + &mut self, + input: &InputState, + animation_time: f32, + id: Id, + value: f32, + ) -> f32 { + match self.values.get_mut(&id) { + None => { + self.values.insert( + id, + ValueAnim { + from_value: value, + to_value: value, + toggle_time: -f64::INFINITY, // long time ago + }, + ); + value + } + Some(anim) => { + let time_since_toggle = (input.time - anim.toggle_time) as f32; + // On the frame we toggle we don't want to return the old value, + // so we extrapolate forwards: + let time_since_toggle = time_since_toggle + input.predicted_dt; + let current_value = remap_clamp( + time_since_toggle, + 0.0..=animation_time, + anim.from_value..=anim.to_value, + ); + if anim.to_value != value { + anim.from_value = current_value; //start new animation from current position of playing animation + anim.to_value = value; + anim.toggle_time = input.time; + } + if animation_time == 0.0 { + anim.from_value = value; + anim.to_value = value; + } + current_value + } + } + } +} diff --git a/nevmes-gui/crates/egui/src/containers/area.rs b/nevmes-gui/crates/egui/src/containers/area.rs new file mode 100644 index 0000000..380c175 --- /dev/null +++ b/nevmes-gui/crates/egui/src/containers/area.rs @@ -0,0 +1,514 @@ +//! Area is a [`Ui`] that has no parent, it floats on the background. +//! It has no frame or own size. It is potentially movable. +//! It is the foundation for windows and popups. + +use crate::*; + +/// State that is persisted between frames. +// TODO(emilk): this is not currently stored in `Memory::data`, but maybe it should be? +#[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub(crate) struct State { + /// Last known pos of the pivot + pub pivot_pos: Pos2, + + pub pivot: Align2, + + /// Last know size. Used for catching clicks. + pub size: Vec2, + + /// If false, clicks goes straight through to what is behind us. + /// Good for tooltips etc. + pub interactable: bool, +} + +impl State { + pub fn left_top_pos(&self) -> Pos2 { + pos2( + self.pivot_pos.x - self.pivot.x().to_factor() * self.size.x, + self.pivot_pos.y - self.pivot.y().to_factor() * self.size.y, + ) + } + + pub fn set_left_top_pos(&mut self, pos: Pos2) { + self.pivot_pos = pos2( + pos.x + self.pivot.x().to_factor() * self.size.x, + pos.y + self.pivot.y().to_factor() * self.size.y, + ); + } + + pub fn rect(&self) -> Rect { + Rect::from_min_size(self.left_top_pos(), self.size) + } +} + +/// An area on the screen that can be moved by dragging. +/// +/// This forms the base of the [`Window`] container. +/// +/// ``` +/// # egui::__run_test_ctx(|ctx| { +/// egui::Area::new("my_area") +/// .fixed_pos(egui::pos2(32.0, 32.0)) +/// .show(ctx, |ui| { +/// ui.label("Floating text!"); +/// }); +/// # }); +/// ``` +#[must_use = "You should call .show()"] +#[derive(Clone, Copy, Debug)] +pub struct Area { + pub(crate) id: Id, + movable: bool, + interactable: bool, + enabled: bool, + constrain: bool, + order: Order, + default_pos: Option, + pivot: Align2, + anchor: Option<(Align2, Vec2)>, + new_pos: Option, + drag_bounds: Option, +} + +impl Area { + pub fn new(id: impl Into) -> Self { + Self { + id: id.into(), + movable: true, + interactable: true, + constrain: false, + enabled: true, + order: Order::Middle, + default_pos: None, + new_pos: None, + pivot: Align2::LEFT_TOP, + anchor: None, + drag_bounds: None, + } + } + + pub fn id(mut self, id: Id) -> Self { + self.id = id; + self + } + + pub fn layer(&self) -> LayerId { + LayerId::new(self.order, self.id) + } + + /// If false, no content responds to click + /// and widgets will be shown grayed out. + /// You won't be able to move the window. + /// Default: `true`. + pub fn enabled(mut self, enabled: bool) -> Self { + self.enabled = enabled; + self + } + + /// moveable by dragging the area? + pub fn movable(mut self, movable: bool) -> Self { + self.movable = movable; + self.interactable |= movable; + self + } + + pub fn is_enabled(&self) -> bool { + self.enabled + } + + pub fn is_movable(&self) -> bool { + self.movable && self.enabled + } + + /// If false, clicks goes straight through to what is behind us. + /// Good for tooltips etc. + pub fn interactable(mut self, interactable: bool) -> Self { + self.interactable = interactable; + self.movable &= interactable; + self + } + + /// `order(Order::Foreground)` for an Area that should always be on top + pub fn order(mut self, order: Order) -> Self { + self.order = order; + self + } + + pub fn default_pos(mut self, default_pos: impl Into) -> Self { + self.default_pos = Some(default_pos.into()); + self + } + + /// Positions the window and prevents it from being moved + pub fn fixed_pos(mut self, fixed_pos: impl Into) -> Self { + self.new_pos = Some(fixed_pos.into()); + self.movable = false; + self + } + + /// Constrains this area to the screen bounds. + pub fn constrain(mut self, constrain: bool) -> Self { + self.constrain = constrain; + self + } + + /// Where the "root" of the area is. + /// + /// For instance, if you set this to [`Align2::RIGHT_TOP`] + /// then [`Self::fixed_pos`] will set the position of the right-top + /// corner of the area. + /// + /// Default: [`Align2::LEFT_TOP`]. + pub fn pivot(mut self, pivot: Align2) -> Self { + self.pivot = pivot; + self + } + + /// Positions the window but you can still move it. + pub fn current_pos(mut self, current_pos: impl Into) -> Self { + self.new_pos = Some(current_pos.into()); + self + } + + /// Set anchor and distance. + /// + /// An anchor of `Align2::RIGHT_TOP` means "put the right-top corner of the window + /// in the right-top corner of the screen". + /// + /// The offset is added to the position, so e.g. an offset of `[-5.0, 5.0]` + /// would move the window left and down from the given anchor. + /// + /// Anchoring also makes the window immovable. + /// + /// It is an error to set both an anchor and a position. + pub fn anchor(mut self, align: Align2, offset: impl Into) -> Self { + self.anchor = Some((align, offset.into())); + self.movable(false) + } + + /// Constrain the area up to which the window can be dragged. + pub fn drag_bounds(mut self, bounds: Rect) -> Self { + self.drag_bounds = Some(bounds); + self + } + + pub(crate) fn get_pivot(&self) -> Align2 { + if let Some((pivot, _)) = self.anchor { + pivot + } else { + Align2::LEFT_TOP + } + } +} + +pub(crate) struct Prepared { + layer_id: LayerId, + state: State, + move_response: Response, + enabled: bool, + drag_bounds: Option, + + /// We always make windows invisible the first frame to hide "first-frame-jitters". + /// + /// This is so that we use the first frame to calculate the window size, + /// and then can correctly position the window and its contents the next frame, + /// without having one frame where the window is wrongly positioned or sized. + temporarily_invisible: bool, +} + +impl Area { + pub fn show( + self, + ctx: &Context, + add_contents: impl FnOnce(&mut Ui) -> R, + ) -> InnerResponse { + let prepared = self.begin(ctx); + let mut content_ui = prepared.content_ui(ctx); + let inner = add_contents(&mut content_ui); + let response = prepared.end(ctx, content_ui); + InnerResponse { inner, response } + } + + pub(crate) fn begin(self, ctx: &Context) -> Prepared { + let Area { + id, + movable, + order, + interactable, + enabled, + default_pos, + new_pos, + pivot, + anchor, + drag_bounds, + constrain, + } = self; + + let layer_id = LayerId::new(order, id); + + let state = ctx.memory(|mem| mem.areas.get(id).copied()); + let is_new = state.is_none(); + if is_new { + ctx.request_repaint(); // if we don't know the previous size we are likely drawing the area in the wrong place + } + let mut state = state.unwrap_or_else(|| State { + pivot_pos: default_pos.unwrap_or_else(|| automatic_area_position(ctx)), + pivot, + size: Vec2::ZERO, + interactable, + }); + state.pivot_pos = new_pos.unwrap_or(state.pivot_pos); + state.interactable = interactable; + + if let Some((anchor, offset)) = anchor { + let screen = ctx.available_rect(); + state.set_left_top_pos( + anchor.align_size_within_rect(state.size, screen).left_top() + offset, + ); + } + + // interact right away to prevent frame-delay + let move_response = { + let interact_id = layer_id.id.with("move"); + let sense = if movable { + Sense::click_and_drag() + } else if interactable { + Sense::click() // allow clicks to bring to front + } else { + Sense::hover() + }; + + let move_response = ctx.interact( + Rect::EVERYTHING, + ctx.style().spacing.item_spacing, + layer_id, + interact_id, + state.rect(), + sense, + enabled, + ); + + // Important check - don't try to move e.g. a combobox popup! + if movable { + if move_response.dragged() { + state.pivot_pos += ctx.input(|i| i.pointer.delta()); + } + + state.set_left_top_pos( + ctx.constrain_window_rect_to_area(state.rect(), drag_bounds) + .min, + ); + } + + if (move_response.dragged() || move_response.clicked()) + || pointer_pressed_on_area(ctx, layer_id) + || !ctx.memory(|m| m.areas.visible_last_frame(&layer_id)) + { + ctx.memory_mut(|m| m.areas.move_to_top(layer_id)); + ctx.request_repaint(); + } + + move_response + }; + + state.set_left_top_pos(ctx.round_pos_to_pixels(state.left_top_pos())); + + if constrain { + state.set_left_top_pos( + ctx.constrain_window_rect_to_area(state.rect(), drag_bounds) + .left_top(), + ); + } + + Prepared { + layer_id, + state, + move_response, + enabled, + drag_bounds, + temporarily_invisible: is_new, + } + } + + pub fn show_open_close_animation(&self, ctx: &Context, frame: &Frame, is_open: bool) { + // must be called first so animation managers know the latest state + let visibility_factor = ctx.animate_bool(self.id.with("close_animation"), is_open); + + if is_open { + // we actually only show close animations. + // when opening a window we show it right away. + return; + } + if visibility_factor <= 0.0 { + return; + } + + let layer_id = LayerId::new(self.order, self.id); + let area_rect = ctx.memory(|mem| mem.areas.get(self.id).map(|area| area.rect())); + if let Some(area_rect) = area_rect { + let clip_rect = ctx.available_rect(); + let painter = Painter::new(ctx.clone(), layer_id, clip_rect); + + // shrinkage: looks kinda a bad on its own + // let area_rect = + // Rect::from_center_size(area_rect.center(), visibility_factor * area_rect.size()); + + let frame = frame.multiply_with_opacity(visibility_factor); + painter.add(frame.paint(area_rect)); + } + } +} + +impl Prepared { + pub(crate) fn state(&self) -> &State { + &self.state + } + + pub(crate) fn state_mut(&mut self) -> &mut State { + &mut self.state + } + + pub(crate) fn drag_bounds(&self) -> Option { + self.drag_bounds + } + + pub(crate) fn content_ui(&self, ctx: &Context) -> Ui { + let screen_rect = ctx.screen_rect(); + + let bounds = if let Some(bounds) = self.drag_bounds { + bounds.intersect(screen_rect) // protect against infinite bounds + } else { + let central_area = ctx.available_rect(); + + let is_within_central_area = central_area.contains_rect(self.state.rect().shrink(1.0)); + if is_within_central_area { + central_area // let's try to not cover side panels + } else { + screen_rect + } + }; + + let max_rect = Rect::from_min_max( + self.state.left_top_pos(), + bounds + .max + .at_least(self.state.left_top_pos() + Vec2::splat(32.0)), + ); + + let shadow_radius = ctx.style().visuals.window_shadow.extrusion; // hacky + let clip_rect_margin = ctx.style().visuals.clip_rect_margin.max(shadow_radius); + + let clip_rect = Rect::from_min_max(self.state.left_top_pos(), bounds.max) + .expand(clip_rect_margin) + .intersect(bounds); + + let mut ui = Ui::new( + ctx.clone(), + self.layer_id, + self.layer_id.id, + max_rect, + clip_rect, + ); + ui.set_enabled(self.enabled); + ui.set_visible(!self.temporarily_invisible); + ui + } + + #[allow(clippy::needless_pass_by_value)] // intentional to swallow up `content_ui`. + pub(crate) fn end(self, ctx: &Context, content_ui: Ui) -> Response { + let Prepared { + layer_id, + mut state, + move_response, + enabled: _, + drag_bounds: _, + temporarily_invisible: _, + } = self; + + state.size = content_ui.min_rect().size(); + + ctx.memory_mut(|m| m.areas.set_state(layer_id, state)); + + move_response + } +} + +fn pointer_pressed_on_area(ctx: &Context, layer_id: LayerId) -> bool { + if let Some(pointer_pos) = ctx.pointer_interact_pos() { + let any_pressed = ctx.input(|i| i.pointer.any_pressed()); + any_pressed && ctx.layer_id_at(pointer_pos) == Some(layer_id) + } else { + false + } +} + +fn automatic_area_position(ctx: &Context) -> Pos2 { + let mut existing: Vec = ctx.memory(|mem| { + mem.areas + .visible_windows() + .into_iter() + .map(State::rect) + .collect() + }); + existing.sort_by_key(|r| r.left().round() as i32); + + let available_rect = ctx.available_rect(); + + let spacing = 16.0; + let left = available_rect.left() + spacing; + let top = available_rect.top() + spacing; + + if existing.is_empty() { + return pos2(left, top); + } + + // Separate existing rectangles into columns: + let mut column_bbs = vec![existing[0]]; + + for &rect in &existing { + let current_column_bb = column_bbs.last_mut().unwrap(); + if rect.left() < current_column_bb.right() { + // same column + *current_column_bb = current_column_bb.union(rect); + } else { + // new column + column_bbs.push(rect); + } + } + + { + // Look for large spaces between columns (empty columns): + let mut x = left; + for col_bb in &column_bbs { + let available = col_bb.left() - x; + if available >= 300.0 { + return pos2(x, top); + } + x = col_bb.right() + spacing; + } + } + + // Find first column with some available space at the bottom of it: + for col_bb in &column_bbs { + if col_bb.bottom() < available_rect.center().y { + return pos2(col_bb.left(), col_bb.bottom() + spacing); + } + } + + // Maybe we can fit a new column? + let rightmost = column_bbs.last().unwrap().right(); + if rightmost + 200.0 < available_rect.right() { + return pos2(rightmost + spacing, top); + } + + // Ok, just put us in the column with the most space at the bottom: + let mut best_pos = pos2(left, column_bbs[0].bottom() + spacing); + for col_bb in &column_bbs { + let col_pos = pos2(col_bb.left(), col_bb.bottom() + spacing); + if col_pos.y < best_pos.y { + best_pos = col_pos; + } + } + best_pos +} diff --git a/nevmes-gui/crates/egui/src/containers/collapsing_header.rs b/nevmes-gui/crates/egui/src/containers/collapsing_header.rs new file mode 100644 index 0000000..00f4ff7 --- /dev/null +++ b/nevmes-gui/crates/egui/src/containers/collapsing_header.rs @@ -0,0 +1,681 @@ +use std::hash::Hash; + +use crate::*; +use epaint::Shape; + +#[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub(crate) struct InnerState { + open: bool, + + /// Height of the region when open. Used for animations + #[cfg_attr(feature = "serde", serde(default))] + open_height: Option, +} + +/// This is a a building block for building collapsing regions. +/// +/// It is used by [`CollapsingHeader`] and [`Window`], but can also be used on its own. +/// +/// See [`CollapsingState::show_header`] for how to show a collapsing header with a custom header. +#[derive(Clone, Debug)] +pub struct CollapsingState { + id: Id, + state: InnerState, +} + +impl CollapsingState { + pub fn load(ctx: &Context, id: Id) -> Option { + ctx.data_mut(|d| { + d.get_persisted::(id) + .map(|state| Self { id, state }) + }) + } + + pub fn store(&self, ctx: &Context) { + ctx.data_mut(|d| d.insert_persisted(self.id, self.state)); + } + + pub fn id(&self) -> Id { + self.id + } + + pub fn load_with_default_open(ctx: &Context, id: Id, default_open: bool) -> Self { + Self::load(ctx, id).unwrap_or(CollapsingState { + id, + state: InnerState { + open: default_open, + open_height: None, + }, + }) + } + + pub fn is_open(&self) -> bool { + self.state.open + } + + pub fn set_open(&mut self, open: bool) { + self.state.open = open; + } + + pub fn toggle(&mut self, ui: &Ui) { + self.state.open = !self.state.open; + ui.ctx().request_repaint(); + } + + /// 0 for closed, 1 for open, with tweening + pub fn openness(&self, ctx: &Context) -> f32 { + if ctx.memory(|mem| mem.everything_is_visible()) { + 1.0 + } else { + ctx.animate_bool(self.id, self.state.open) + } + } + + /// Will toggle when clicked, etc. + pub(crate) fn show_default_button_with_size( + &mut self, + ui: &mut Ui, + button_size: Vec2, + ) -> Response { + let (_id, rect) = ui.allocate_space(button_size); + let response = ui.interact(rect, self.id, Sense::click()); + if response.clicked() { + self.toggle(ui); + } + let openness = self.openness(ui.ctx()); + paint_default_icon(ui, openness, &response); + response + } + + /// Will toggle when clicked, etc. + fn show_default_button_indented(&mut self, ui: &mut Ui) -> Response { + self.show_button_indented(ui, paint_default_icon) + } + + /// Will toggle when clicked, etc. + fn show_button_indented( + &mut self, + ui: &mut Ui, + icon_fn: impl FnOnce(&mut Ui, f32, &Response) + 'static, + ) -> Response { + let size = vec2(ui.spacing().indent, ui.spacing().icon_width); + let (_id, rect) = ui.allocate_space(size); + let response = ui.interact(rect, self.id, Sense::click()); + if response.clicked() { + self.toggle(ui); + } + + let (mut icon_rect, _) = ui.spacing().icon_rectangles(response.rect); + icon_rect.set_center(pos2( + response.rect.left() + ui.spacing().indent / 2.0, + response.rect.center().y, + )); + let openness = self.openness(ui.ctx()); + let small_icon_response = response.clone().with_new_rect(icon_rect); + icon_fn(ui, openness, &small_icon_response); + response + } + + /// Shows header and body (if expanded). + /// + /// The header will start with the default button in a horizontal layout, followed by whatever you add. + /// + /// Will also store the state. + /// + /// Returns the response of the collapsing button, the custom header, and the custom body. + /// + /// ``` + /// # egui::__run_test_ui(|ui| { + /// let id = ui.make_persistent_id("my_collapsing_header"); + /// egui::collapsing_header::CollapsingState::load_with_default_open(ui.ctx(), id, false) + /// .show_header(ui, |ui| { + /// ui.label("Header"); // you can put checkboxes or whatever here + /// }) + /// .body(|ui| ui.label("Body")); + /// # }); + /// ``` + pub fn show_header( + mut self, + ui: &mut Ui, + add_header: impl FnOnce(&mut Ui) -> HeaderRet, + ) -> HeaderResponse<'_, HeaderRet> { + let header_response = ui.horizontal(|ui| { + let prev_item_spacing = ui.spacing_mut().item_spacing; + ui.spacing_mut().item_spacing.x = 0.0; // the toggler button uses the full indent width + let collapser = self.show_default_button_indented(ui); + ui.spacing_mut().item_spacing = prev_item_spacing; + (collapser, add_header(ui)) + }); + HeaderResponse { + state: self, + ui, + toggle_button_response: header_response.inner.0, + header_response: InnerResponse { + response: header_response.response, + inner: header_response.inner.1, + }, + } + } + + /// Show body if we are open, with a nice animation between closed and open. + /// Indent the body to show it belongs to the header. + /// + /// Will also store the state. + pub fn show_body_indented( + &mut self, + header_response: &Response, + ui: &mut Ui, + add_body: impl FnOnce(&mut Ui) -> R, + ) -> Option> { + let id = self.id; + self.show_body_unindented(ui, |ui| { + ui.indent(id, |ui| { + // make as wide as the header: + ui.expand_to_include_x(header_response.rect.right()); + add_body(ui) + }) + .inner + }) + } + + /// Show body if we are open, with a nice animation between closed and open. + /// Will also store the state. + pub fn show_body_unindented( + &mut self, + ui: &mut Ui, + add_body: impl FnOnce(&mut Ui) -> R, + ) -> Option> { + let openness = self.openness(ui.ctx()); + if openness <= 0.0 { + self.store(ui.ctx()); // we store any earlier toggling as promised in the docstring + None + } else if openness < 1.0 { + Some(ui.scope(|child_ui| { + let max_height = if self.state.open && self.state.open_height.is_none() { + // First frame of expansion. + // We don't know full height yet, but we will next frame. + // Just use a placeholder value that shows some movement: + 10.0 + } else { + let full_height = self.state.open_height.unwrap_or_default(); + remap_clamp(openness, 0.0..=1.0, 0.0..=full_height) + }; + + let mut clip_rect = child_ui.clip_rect(); + clip_rect.max.y = clip_rect.max.y.min(child_ui.max_rect().top() + max_height); + child_ui.set_clip_rect(clip_rect); + + let ret = add_body(child_ui); + + let mut min_rect = child_ui.min_rect(); + self.state.open_height = Some(min_rect.height()); + self.store(child_ui.ctx()); // remember the height + + // Pretend children took up at most `max_height` space: + min_rect.max.y = min_rect.max.y.at_most(min_rect.top() + max_height); + child_ui.force_set_min_rect(min_rect); + ret + })) + } else { + let ret_response = ui.scope(add_body); + let full_size = ret_response.response.rect.size(); + self.state.open_height = Some(full_size.y); + self.store(ui.ctx()); // remember the height + Some(ret_response) + } + } + + /// Paint this [CollapsingState](CollapsingState)'s toggle button. Takes an [IconPainter](IconPainter) as the icon. + /// ``` + /// # egui::__run_test_ui(|ui| { + /// fn circle_icon(ui: &mut egui::Ui, openness: f32, response: &egui::Response) { + /// let stroke = ui.style().interact(&response).fg_stroke; + /// let radius = egui::lerp(2.0..=3.0, openness); + /// ui.painter().circle_filled(response.rect.center(), radius, stroke.color); + /// } + /// + /// let mut state = egui::collapsing_header::CollapsingState::load_with_default_open( + /// ui.ctx(), + /// ui.make_persistent_id("my_collapsing_state"), + /// false, + /// ); + /// + /// let header_res = ui.horizontal(|ui| { + /// ui.label("Header"); + /// state.show_toggle_button(ui, circle_icon); + /// }); + /// + /// state.show_body_indented(&header_res.response, ui, |ui| ui.label("Body")); + /// # }); + /// ``` + pub fn show_toggle_button( + &mut self, + ui: &mut Ui, + icon_fn: impl FnOnce(&mut Ui, f32, &Response) + 'static, + ) -> Response { + self.show_button_indented(ui, icon_fn) + } +} + +/// From [`CollapsingState::show_header`]. +#[must_use = "Remember to show the body"] +pub struct HeaderResponse<'ui, HeaderRet> { + state: CollapsingState, + ui: &'ui mut Ui, + toggle_button_response: Response, + header_response: InnerResponse, +} + +impl<'ui, HeaderRet> HeaderResponse<'ui, HeaderRet> { + /// Returns the response of the collapsing button, the custom header, and the custom body. + pub fn body( + mut self, + add_body: impl FnOnce(&mut Ui) -> BodyRet, + ) -> ( + Response, + InnerResponse, + Option>, + ) { + let body_response = + self.state + .show_body_indented(&self.header_response.response, self.ui, add_body); + ( + self.toggle_button_response, + self.header_response, + body_response, + ) + } + + /// Returns the response of the collapsing button, the custom header, and the custom body, without indentation. + pub fn body_unindented( + mut self, + add_body: impl FnOnce(&mut Ui) -> BodyRet, + ) -> ( + Response, + InnerResponse, + Option>, + ) { + let body_response = self.state.show_body_unindented(self.ui, add_body); + ( + self.toggle_button_response, + self.header_response, + body_response, + ) + } +} + +// ---------------------------------------------------------------------------- + +/// Paint the arrow icon that indicated if the region is open or not +pub fn paint_default_icon(ui: &mut Ui, openness: f32, response: &Response) { + let visuals = ui.style().interact(response); + + let rect = response.rect; + + // Draw a pointy triangle arrow: + let rect = Rect::from_center_size(rect.center(), vec2(rect.width(), rect.height()) * 0.75); + let rect = rect.expand(visuals.expansion); + let mut points = vec![rect.left_top(), rect.right_top(), rect.center_bottom()]; + use std::f32::consts::TAU; + let rotation = emath::Rot2::from_angle(remap(openness, 0.0..=1.0, -TAU / 4.0..=0.0)); + for p in &mut points { + *p = rect.center() + rotation * (*p - rect.center()); + } + + ui.painter().add(Shape::convex_polygon( + points, + visuals.fg_stroke.color, + Stroke::NONE, + )); +} + +/// A function that paints an icon indicating if the region is open or not +pub type IconPainter = Box; + +/// A header which can be collapsed/expanded, revealing a contained [`Ui`] region. +/// +/// ``` +/// # egui::__run_test_ui(|ui| { +/// egui::CollapsingHeader::new("Heading") +/// .show(ui, |ui| { +/// ui.label("Body"); +/// }); +/// +/// // Short version: +/// ui.collapsing("Heading", |ui| { ui.label("Body"); }); +/// # }); +/// ``` +/// +/// If you want to customize the header contents, see [`CollapsingState::show_header`]. +#[must_use = "You should call .show()"] +pub struct CollapsingHeader { + text: WidgetText, + default_open: bool, + open: Option, + id_source: Id, + enabled: bool, + selectable: bool, + selected: bool, + show_background: bool, + icon: Option, +} + +impl CollapsingHeader { + /// The [`CollapsingHeader`] starts out collapsed unless you call `default_open`. + /// + /// The label is used as an [`Id`] source. + /// If the label is unique and static this is fine, + /// but if it changes or there are several [`CollapsingHeader`] with the same title + /// you need to provide a unique id source with [`Self::id_source`]. + pub fn new(text: impl Into) -> Self { + let text = text.into(); + let id_source = Id::new(text.text()); + Self { + text, + default_open: false, + open: None, + id_source, + enabled: true, + selectable: false, + selected: false, + show_background: false, + icon: None, + } + } + + /// By default, the [`CollapsingHeader`] is collapsed. + /// Call `.default_open(true)` to change this. + pub fn default_open(mut self, open: bool) -> Self { + self.default_open = open; + self + } + + /// Calling `.open(Some(true))` will make the collapsing header open this frame (or stay open). + /// + /// Calling `.open(Some(false))` will make the collapsing header close this frame (or stay closed). + /// + /// Calling `.open(None)` has no effect (default). + pub fn open(mut self, open: Option) -> Self { + self.open = open; + self + } + + /// Explicitly set the source of the [`Id`] of this widget, instead of using title label. + /// This is useful if the title label is dynamic or not unique. + pub fn id_source(mut self, id_source: impl Hash) -> Self { + self.id_source = Id::new(id_source); + self + } + + /// If you set this to `false`, the [`CollapsingHeader`] will be grayed out and un-clickable. + /// + /// This is a convenience for [`Ui::set_enabled`]. + pub fn enabled(mut self, enabled: bool) -> Self { + self.enabled = enabled; + self + } + + /// Can the [`CollapsingHeader`] be selected by clicking it? Default: `false`. + #[deprecated = "Use the more powerful egui::collapsing_header::CollapsingState::show_header"] // Deprecated in 2022-04-28, before egui 0.18 + pub fn selectable(mut self, selectable: bool) -> Self { + self.selectable = selectable; + self + } + + /// If you set this to 'true', the [`CollapsingHeader`] will be shown as selected. + /// + /// Example: + /// ``` + /// # egui::__run_test_ui(|ui| { + /// let mut selected = false; + /// let response = egui::CollapsingHeader::new("Select and open me") + /// .selectable(true) + /// .selected(selected) + /// .show(ui, |ui| ui.label("Body")); + /// if response.header_response.clicked() { + /// selected = true; + /// } + /// # }); + /// ``` + #[deprecated = "Use the more powerful egui::collapsing_header::CollapsingState::show_header"] // Deprecated in 2022-04-28, before egui 0.18 + pub fn selected(mut self, selected: bool) -> Self { + self.selected = selected; + self + } + + /// Should the [`CollapsingHeader`] show a background behind it? Default: `false`. + /// + /// To show it behind all [`CollapsingHeader`] you can just use: + /// ``` + /// # egui::__run_test_ui(|ui| { + /// ui.visuals_mut().collapsing_header_frame = true; + /// # }); + /// ``` + pub fn show_background(mut self, show_background: bool) -> Self { + self.show_background = show_background; + self + } + + /// Use the provided function to render a different [`CollapsingHeader`] icon. + /// Defaults to a triangle that animates as the [`CollapsingHeader`] opens and closes. + /// + /// For example: + /// ``` + /// # egui::__run_test_ui(|ui| { + /// fn circle_icon(ui: &mut egui::Ui, openness: f32, response: &egui::Response) { + /// let stroke = ui.style().interact(&response).fg_stroke; + /// let radius = egui::lerp(2.0..=3.0, openness); + /// ui.painter().circle_filled(response.rect.center(), radius, stroke.color); + /// } + /// + /// egui::CollapsingHeader::new("Circles") + /// .icon(circle_icon) + /// .show(ui, |ui| { ui.label("Hi!"); }); + /// # }); + /// ``` + pub fn icon(mut self, icon_fn: impl FnOnce(&mut Ui, f32, &Response) + 'static) -> Self { + self.icon = Some(Box::new(icon_fn)); + self + } +} + +struct Prepared { + header_response: Response, + state: CollapsingState, + openness: f32, +} + +impl CollapsingHeader { + fn begin(self, ui: &mut Ui) -> Prepared { + assert!( + ui.layout().main_dir().is_vertical(), + "Horizontal collapsing is unimplemented" + ); + let Self { + icon, + text, + default_open, + open, + id_source, + enabled: _, + selectable, + selected, + show_background, + } = self; + + // TODO(emilk): horizontal layout, with icon and text as labels. Insert background behind using Frame. + + let id = ui.make_persistent_id(id_source); + let button_padding = ui.spacing().button_padding; + + let available = ui.available_rect_before_wrap(); + let text_pos = available.min + vec2(ui.spacing().indent, 0.0); + let wrap_width = available.right() - text_pos.x; + let wrap = Some(false); + let text = text.into_galley(ui, wrap, wrap_width, TextStyle::Button); + let text_max_x = text_pos.x + text.size().x; + + let mut desired_width = text_max_x + button_padding.x - available.left(); + if ui.visuals().collapsing_header_frame { + desired_width = desired_width.max(available.width()); // fill full width + } + + let mut desired_size = vec2(desired_width, text.size().y + 2.0 * button_padding.y); + desired_size = desired_size.at_least(ui.spacing().interact_size); + let (_, rect) = ui.allocate_space(desired_size); + + let mut header_response = ui.interact(rect, id, Sense::click()); + let text_pos = pos2( + text_pos.x, + header_response.rect.center().y - text.size().y / 2.0, + ); + + let mut state = CollapsingState::load_with_default_open(ui.ctx(), id, default_open); + if let Some(open) = open { + if open != state.is_open() { + state.toggle(ui); + header_response.mark_changed(); + } + } else if header_response.clicked() { + state.toggle(ui); + header_response.mark_changed(); + } + + header_response + .widget_info(|| WidgetInfo::labeled(WidgetType::CollapsingHeader, text.text())); + + let openness = state.openness(ui.ctx()); + + if ui.is_rect_visible(rect) { + let visuals = ui.style().interact_selectable(&header_response, selected); + + if ui.visuals().collapsing_header_frame || show_background { + ui.painter().add(epaint::RectShape { + rect: header_response.rect.expand(visuals.expansion), + rounding: visuals.rounding, + fill: visuals.weak_bg_fill, + stroke: visuals.bg_stroke, + // stroke: Default::default(), + }); + } + + if selected || selectable && (header_response.hovered() || header_response.has_focus()) + { + let rect = rect.expand(visuals.expansion); + + ui.painter() + .rect(rect, visuals.rounding, visuals.bg_fill, visuals.bg_stroke); + } + + { + let (mut icon_rect, _) = ui.spacing().icon_rectangles(header_response.rect); + icon_rect.set_center(pos2( + header_response.rect.left() + ui.spacing().indent / 2.0, + header_response.rect.center().y, + )); + let icon_response = header_response.clone().with_new_rect(icon_rect); + if let Some(icon) = icon { + icon(ui, openness, &icon_response); + } else { + paint_default_icon(ui, openness, &icon_response); + } + } + + text.paint_with_visuals(ui.painter(), text_pos, &visuals); + } + + Prepared { + header_response, + state, + openness, + } + } + + #[inline] + pub fn show( + self, + ui: &mut Ui, + add_body: impl FnOnce(&mut Ui) -> R, + ) -> CollapsingResponse { + self.show_dyn(ui, Box::new(add_body), true) + } + + #[inline] + pub fn show_unindented( + self, + ui: &mut Ui, + add_body: impl FnOnce(&mut Ui) -> R, + ) -> CollapsingResponse { + self.show_dyn(ui, Box::new(add_body), false) + } + + fn show_dyn<'c, R>( + self, + ui: &mut Ui, + add_body: Box R + 'c>, + indented: bool, + ) -> CollapsingResponse { + // Make sure body is bellow header, + // and make sure it is one unit (necessary for putting a [`CollapsingHeader`] in a grid). + ui.vertical(|ui| { + ui.set_enabled(self.enabled); + + let Prepared { + header_response, + mut state, + openness, + } = self.begin(ui); // show the header + + let ret_response = if indented { + state.show_body_indented(&header_response, ui, add_body) + } else { + state.show_body_unindented(ui, add_body) + }; + + if let Some(ret_response) = ret_response { + CollapsingResponse { + header_response, + body_response: Some(ret_response.response), + body_returned: Some(ret_response.inner), + openness, + } + } else { + CollapsingResponse { + header_response, + body_response: None, + body_returned: None, + openness, + } + } + }) + .inner + } +} + +/// The response from showing a [`CollapsingHeader`]. +pub struct CollapsingResponse { + /// Response of the actual clickable header. + pub header_response: Response, + + /// None iff collapsed. + pub body_response: Option, + + /// None iff collapsed. + pub body_returned: Option, + + /// 0.0 if fully closed, 1.0 if fully open, and something in-between while animating. + pub openness: f32, +} + +impl CollapsingResponse { + /// Was the [`CollapsingHeader`] fully closed (and not being animated)? + pub fn fully_closed(&self) -> bool { + self.openness <= 0.0 + } + + /// Was the [`CollapsingHeader`] fully open (and not being animated)? + pub fn fully_open(&self) -> bool { + self.openness >= 1.0 + } +} diff --git a/nevmes-gui/crates/egui/src/containers/combo_box.rs b/nevmes-gui/crates/egui/src/containers/combo_box.rs new file mode 100644 index 0000000..8ede4d6 --- /dev/null +++ b/nevmes-gui/crates/egui/src/containers/combo_box.rs @@ -0,0 +1,429 @@ +use epaint::Shape; + +use crate::{style::WidgetVisuals, *}; + +/// Indicate wether or not a popup will be shown above or below the box. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum AboveOrBelow { + Above, + Below, +} + +/// A function that paints the [`ComboBox`] icon +pub type IconPainter = Box; + +/// A drop-down selection menu with a descriptive label. +/// +/// ``` +/// # #[derive(Debug, PartialEq)] +/// # enum Enum { First, Second, Third } +/// # let mut selected = Enum::First; +/// # egui::__run_test_ui(|ui| { +/// egui::ComboBox::from_label("Select one!") +/// .selected_text(format!("{:?}", selected)) +/// .show_ui(ui, |ui| { +/// ui.selectable_value(&mut selected, Enum::First, "First"); +/// ui.selectable_value(&mut selected, Enum::Second, "Second"); +/// ui.selectable_value(&mut selected, Enum::Third, "Third"); +/// } +/// ); +/// # }); +/// ``` +#[must_use = "You should call .show*"] +pub struct ComboBox { + id_source: Id, + label: Option, + selected_text: WidgetText, + width: Option, + icon: Option, + wrap_enabled: bool, +} + +impl ComboBox { + /// Create new [`ComboBox`] with id and label + pub fn new(id_source: impl std::hash::Hash, label: impl Into) -> Self { + Self { + id_source: Id::new(id_source), + label: Some(label.into()), + selected_text: Default::default(), + width: None, + icon: None, + wrap_enabled: false, + } + } + + /// Label shown next to the combo box + pub fn from_label(label: impl Into) -> Self { + let label = label.into(); + Self { + id_source: Id::new(label.text()), + label: Some(label), + selected_text: Default::default(), + width: None, + icon: None, + wrap_enabled: false, + } + } + + /// Without label. + pub fn from_id_source(id_source: impl std::hash::Hash) -> Self { + Self { + id_source: Id::new(id_source), + label: Default::default(), + selected_text: Default::default(), + width: None, + icon: None, + wrap_enabled: false, + } + } + + /// Set the outer width of the button and menu. + pub fn width(mut self, width: f32) -> Self { + self.width = Some(width); + self + } + + /// What we show as the currently selected value + pub fn selected_text(mut self, selected_text: impl Into) -> Self { + self.selected_text = selected_text.into(); + self + } + + /// Use the provided function to render a different [`ComboBox`] icon. + /// Defaults to a triangle that expands when the cursor is hovering over the [`ComboBox`]. + /// + /// For example: + /// ``` + /// # egui::__run_test_ui(|ui| { + /// # let text = "Selected text"; + /// pub fn filled_triangle( + /// ui: &egui::Ui, + /// rect: egui::Rect, + /// visuals: &egui::style::WidgetVisuals, + /// _is_open: bool, + /// _above_or_below: egui::AboveOrBelow, + /// ) { + /// let rect = egui::Rect::from_center_size( + /// rect.center(), + /// egui::vec2(rect.width() * 0.6, rect.height() * 0.4), + /// ); + /// ui.painter().add(egui::Shape::convex_polygon( + /// vec![rect.left_top(), rect.right_top(), rect.center_bottom()], + /// visuals.fg_stroke.color, + /// visuals.fg_stroke, + /// )); + /// } + /// + /// egui::ComboBox::from_id_source("my-combobox") + /// .selected_text(text) + /// .icon(filled_triangle) + /// .show_ui(ui, |_ui| {}); + /// # }); + /// ``` + pub fn icon( + mut self, + icon_fn: impl FnOnce(&Ui, Rect, &WidgetVisuals, bool, AboveOrBelow) + 'static, + ) -> Self { + self.icon = Some(Box::new(icon_fn)); + self + } + + /// Controls whether text wrap is used for the selected text + pub fn wrap(mut self, wrap: bool) -> Self { + self.wrap_enabled = wrap; + self + } + + /// Show the combo box, with the given ui code for the menu contents. + /// + /// Returns `InnerResponse { inner: None }` if the combo box is closed. + pub fn show_ui( + self, + ui: &mut Ui, + menu_contents: impl FnOnce(&mut Ui) -> R, + ) -> InnerResponse> { + self.show_ui_dyn(ui, Box::new(menu_contents)) + } + + fn show_ui_dyn<'c, R>( + self, + ui: &mut Ui, + menu_contents: Box R + 'c>, + ) -> InnerResponse> { + let Self { + id_source, + label, + selected_text, + width, + icon, + wrap_enabled, + } = self; + + let button_id = ui.make_persistent_id(id_source); + + ui.horizontal(|ui| { + let mut ir = combo_box_dyn( + ui, + button_id, + selected_text, + menu_contents, + icon, + wrap_enabled, + width, + ); + if let Some(label) = label { + ir.response + .widget_info(|| WidgetInfo::labeled(WidgetType::ComboBox, label.text())); + ir.response |= ui.label(label); + } else { + ir.response + .widget_info(|| WidgetInfo::labeled(WidgetType::ComboBox, "")); + } + ir + }) + .inner + } + + /// Show a list of items with the given selected index. + /// + /// + /// ``` + /// # #[derive(Debug, PartialEq)] + /// # enum Enum { First, Second, Third } + /// # let mut selected = Enum::First; + /// # egui::__run_test_ui(|ui| { + /// let alternatives = ["a", "b", "c", "d"]; + /// let mut selected = 2; + /// egui::ComboBox::from_label("Select one!").show_index( + /// ui, + /// &mut selected, + /// alternatives.len(), + /// |i| alternatives[i].to_owned() + /// ); + /// # }); + /// ``` + pub fn show_index( + self, + ui: &mut Ui, + selected: &mut usize, + len: usize, + get: impl Fn(usize) -> String, + ) -> Response { + let slf = self.selected_text(get(*selected)); + + let mut changed = false; + + let mut response = slf + .show_ui(ui, |ui| { + for i in 0..len { + if ui.selectable_label(i == *selected, get(i)).clicked() { + *selected = i; + changed = true; + } + } + }) + .response; + + if changed { + response.mark_changed(); + } + response + } +} + +fn combo_box_dyn<'c, R>( + ui: &mut Ui, + button_id: Id, + selected_text: WidgetText, + menu_contents: Box R + 'c>, + icon: Option, + wrap_enabled: bool, + width: Option, +) -> InnerResponse> { + let popup_id = button_id.with("popup"); + + let is_popup_open = ui.memory(|m| m.is_popup_open(popup_id)); + + let popup_height = ui.memory(|m| m.areas.get(popup_id).map_or(100.0, |state| state.size.y)); + + let above_or_below = + if ui.next_widget_position().y + ui.spacing().interact_size.y + popup_height + < ui.ctx().screen_rect().bottom() + { + AboveOrBelow::Below + } else { + AboveOrBelow::Above + }; + + let margin = ui.spacing().button_padding; + let button_response = button_frame(ui, button_id, is_popup_open, Sense::click(), |ui| { + let icon_spacing = ui.spacing().icon_spacing; + // We don't want to change width when user selects something new + let full_minimum_width = if wrap_enabled { + // Currently selected value's text will be wrapped if needed, so occupy the available width. + ui.available_width() + } else { + // Occupy at least the minimum width assigned to ComboBox. + let width = width.unwrap_or_else(|| ui.spacing().combo_width); + width - 2.0 * margin.x + }; + let icon_size = Vec2::splat(ui.spacing().icon_width); + let wrap_width = if wrap_enabled { + // Use the available width, currently selected value's text will be wrapped if exceeds this value. + ui.available_width() - icon_spacing - icon_size.x + } else { + // Use all the width necessary to display the currently selected value's text. + f32::INFINITY + }; + + let galley = + selected_text.into_galley(ui, Some(wrap_enabled), wrap_width, TextStyle::Button); + + // The width necessary to contain the whole widget with the currently selected value's text. + let width = if wrap_enabled { + full_minimum_width + } else { + // Occupy at least the minimum width needed to contain the widget with the currently selected value's text. + galley.size().x + icon_spacing + icon_size.x + }; + + // Case : wrap_enabled : occupy all the available width. + // Case : !wrap_enabled : occupy at least the minimum width assigned to Slider and ComboBox, + // increase if the currently selected value needs additional horizontal space to fully display its text (up to wrap_width (f32::INFINITY)). + let width = width.at_least(full_minimum_width); + let height = galley.size().y.max(icon_size.y); + + let (_, rect) = ui.allocate_space(Vec2::new(width, height)); + let button_rect = ui.min_rect().expand2(ui.spacing().button_padding); + let response = ui.interact(button_rect, button_id, Sense::click()); + // response.active |= is_popup_open; + + if ui.is_rect_visible(rect) { + let icon_rect = Align2::RIGHT_CENTER.align_size_within_rect(icon_size, rect); + let visuals = if is_popup_open { + &ui.visuals().widgets.open + } else { + ui.style().interact(&response) + }; + + if let Some(icon) = icon { + icon( + ui, + icon_rect.expand(visuals.expansion), + visuals, + is_popup_open, + above_or_below, + ); + } else { + paint_default_icon( + ui.painter(), + icon_rect.expand(visuals.expansion), + visuals, + above_or_below, + ); + } + + let text_rect = Align2::LEFT_CENTER.align_size_within_rect(galley.size(), rect); + galley.paint_with_visuals(ui.painter(), text_rect.min, visuals); + } + }); + + if button_response.clicked() { + ui.memory_mut(|mem| mem.toggle_popup(popup_id)); + } + let inner = crate::popup::popup_above_or_below_widget( + ui, + popup_id, + &button_response, + above_or_below, + |ui| { + ScrollArea::vertical() + .max_height(ui.spacing().combo_height) + .show(ui, menu_contents) + .inner + }, + ); + + InnerResponse { + inner, + response: button_response, + } +} + +fn button_frame( + ui: &mut Ui, + id: Id, + is_popup_open: bool, + sense: Sense, + add_contents: impl FnOnce(&mut Ui), +) -> Response { + let where_to_put_background = ui.painter().add(Shape::Noop); + + let margin = ui.spacing().button_padding; + let interact_size = ui.spacing().interact_size; + + let mut outer_rect = ui.available_rect_before_wrap(); + outer_rect.set_height(outer_rect.height().at_least(interact_size.y)); + + let inner_rect = outer_rect.shrink2(margin); + let mut content_ui = ui.child_ui(inner_rect, *ui.layout()); + add_contents(&mut content_ui); + + let mut outer_rect = content_ui.min_rect().expand2(margin); + outer_rect.set_height(outer_rect.height().at_least(interact_size.y)); + + let response = ui.interact(outer_rect, id, sense); + + if ui.is_rect_visible(outer_rect) { + let visuals = if is_popup_open { + &ui.visuals().widgets.open + } else { + ui.style().interact(&response) + }; + + ui.painter().set( + where_to_put_background, + epaint::RectShape { + rect: outer_rect.expand(visuals.expansion), + rounding: visuals.rounding, + fill: visuals.weak_bg_fill, + stroke: visuals.bg_stroke, + }, + ); + } + + ui.advance_cursor_after_rect(outer_rect); + + response +} + +fn paint_default_icon( + painter: &Painter, + rect: Rect, + visuals: &WidgetVisuals, + above_or_below: AboveOrBelow, +) { + let rect = Rect::from_center_size( + rect.center(), + vec2(rect.width() * 0.7, rect.height() * 0.45), + ); + + match above_or_below { + AboveOrBelow::Above => { + // Upward pointing triangle + painter.add(Shape::convex_polygon( + vec![rect.left_bottom(), rect.right_bottom(), rect.center_top()], + visuals.fg_stroke.color, + Stroke::NONE, + )); + } + AboveOrBelow::Below => { + // Downward pointing triangle + painter.add(Shape::convex_polygon( + vec![rect.left_top(), rect.right_top(), rect.center_bottom()], + visuals.fg_stroke.color, + Stroke::NONE, + )); + } + } +} diff --git a/nevmes-gui/crates/egui/src/containers/frame.rs b/nevmes-gui/crates/egui/src/containers/frame.rs new file mode 100644 index 0000000..fa1caee --- /dev/null +++ b/nevmes-gui/crates/egui/src/containers/frame.rs @@ -0,0 +1,288 @@ +//! Frame container + +use crate::{layers::ShapeIdx, style::Margin, *}; +use epaint::*; + +/// Add a background, frame and/or margin to a rectangular background of a [`Ui`]. +/// +/// ``` +/// # egui::__run_test_ui(|ui| { +/// egui::Frame::none() +/// .fill(egui::Color32::RED) +/// .show(ui, |ui| { +/// ui.label("Label with red background"); +/// }); +/// # }); +/// ``` +#[derive(Clone, Copy, Debug, Default, PartialEq)] +#[must_use = "You should call .show()"] +pub struct Frame { + /// Margin within the painted frame. + pub inner_margin: Margin, + + /// Margin outside the painted frame. + pub outer_margin: Margin, + + pub rounding: Rounding, + + pub shadow: Shadow, + + pub fill: Color32, + + pub stroke: Stroke, +} + +impl Frame { + pub fn none() -> Self { + Self::default() + } + + /// For when you want to group a few widgets together within a frame. + pub fn group(style: &Style) -> Self { + Self { + inner_margin: Margin::same(6.0), // same and symmetric looks best in corners when nesting groups + rounding: style.visuals.widgets.noninteractive.rounding, + stroke: style.visuals.widgets.noninteractive.bg_stroke, + ..Default::default() + } + } + + pub fn side_top_panel(style: &Style) -> Self { + Self { + inner_margin: Margin::symmetric(8.0, 2.0), + fill: style.visuals.panel_fill, + ..Default::default() + } + } + + pub fn central_panel(style: &Style) -> Self { + Self { + inner_margin: Margin::same(8.0), + fill: style.visuals.panel_fill, + ..Default::default() + } + } + + pub fn window(style: &Style) -> Self { + Self { + inner_margin: style.spacing.window_margin, + rounding: style.visuals.window_rounding, + shadow: style.visuals.window_shadow, + fill: style.visuals.window_fill(), + stroke: style.visuals.window_stroke(), + ..Default::default() + } + } + + pub fn menu(style: &Style) -> Self { + Self { + inner_margin: style.spacing.menu_margin, + rounding: style.visuals.menu_rounding, + shadow: style.visuals.popup_shadow, + fill: style.visuals.window_fill(), + stroke: style.visuals.window_stroke(), + ..Default::default() + } + } + + pub fn popup(style: &Style) -> Self { + Self { + inner_margin: style.spacing.menu_margin, + rounding: style.visuals.menu_rounding, + shadow: style.visuals.popup_shadow, + fill: style.visuals.window_fill(), + stroke: style.visuals.window_stroke(), + ..Default::default() + } + } + + /// A canvas to draw on. + /// + /// In bright mode this will be very bright, + /// and in dark mode this will be very dark. + pub fn canvas(style: &Style) -> Self { + Self { + inner_margin: Margin::same(2.0), + rounding: style.visuals.widgets.noninteractive.rounding, + fill: style.visuals.extreme_bg_color, + stroke: style.visuals.window_stroke(), + ..Default::default() + } + } + + /// A dark canvas to draw on. + pub fn dark_canvas(style: &Style) -> Self { + Self { + fill: Color32::from_black_alpha(250), + ..Self::canvas(style) + } + } +} + +impl Frame { + #[inline] + pub fn fill(mut self, fill: Color32) -> Self { + self.fill = fill; + self + } + + #[inline] + pub fn stroke(mut self, stroke: Stroke) -> Self { + self.stroke = stroke; + self + } + + #[inline] + pub fn rounding(mut self, rounding: impl Into) -> Self { + self.rounding = rounding.into(); + self + } + + /// Margin within the painted frame. + #[inline] + pub fn inner_margin(mut self, inner_margin: impl Into) -> Self { + self.inner_margin = inner_margin.into(); + self + } + + /// Margin outside the painted frame. + #[inline] + pub fn outer_margin(mut self, outer_margin: impl Into) -> Self { + self.outer_margin = outer_margin.into(); + self + } + + #[deprecated = "Renamed inner_margin in egui 0.18"] + #[inline] + pub fn margin(self, margin: impl Into) -> Self { + self.inner_margin(margin) + } + + #[inline] + pub fn shadow(mut self, shadow: Shadow) -> Self { + self.shadow = shadow; + self + } + + pub fn multiply_with_opacity(mut self, opacity: f32) -> Self { + self.fill = self.fill.linear_multiply(opacity); + self.stroke.color = self.stroke.color.linear_multiply(opacity); + self.shadow.color = self.shadow.color.linear_multiply(opacity); + self + } +} + +impl Frame { + /// inner margin plus outer margin. + #[inline] + pub fn total_margin(&self) -> Margin { + self.inner_margin + self.outer_margin + } +} + +// ---------------------------------------------------------------------------- + +pub struct Prepared { + pub frame: Frame, + where_to_put_background: ShapeIdx, + pub content_ui: Ui, +} + +impl Frame { + pub fn begin(self, ui: &mut Ui) -> Prepared { + let where_to_put_background = ui.painter().add(Shape::Noop); + let outer_rect_bounds = ui.available_rect_before_wrap(); + + let mut inner_rect = outer_rect_bounds; + inner_rect.min += self.outer_margin.left_top() + self.inner_margin.left_top(); + inner_rect.max -= self.outer_margin.right_bottom() + self.inner_margin.right_bottom(); + + // Make sure we don't shrink to the negative: + inner_rect.max.x = inner_rect.max.x.max(inner_rect.min.x); + inner_rect.max.y = inner_rect.max.y.max(inner_rect.min.y); + + let content_ui = ui.child_ui(inner_rect, *ui.layout()); + + // content_ui.set_clip_rect(outer_rect_bounds.shrink(self.stroke.width * 0.5)); // Can't do this since we don't know final size yet + + Prepared { + frame: self, + where_to_put_background, + content_ui, + } + } + + pub fn show(self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> InnerResponse { + self.show_dyn(ui, Box::new(add_contents)) + } + + fn show_dyn<'c, R>( + self, + ui: &mut Ui, + add_contents: Box R + 'c>, + ) -> InnerResponse { + let mut prepared = self.begin(ui); + let ret = add_contents(&mut prepared.content_ui); + let response = prepared.end(ui); + InnerResponse::new(ret, response) + } + + pub fn paint(&self, outer_rect: Rect) -> Shape { + let Self { + inner_margin: _, + outer_margin: _, + rounding, + shadow, + fill, + stroke, + } = *self; + + let frame_shape = Shape::Rect(epaint::RectShape { + rect: outer_rect, + rounding, + fill, + stroke, + }); + + if shadow == Default::default() { + frame_shape + } else { + let shadow = shadow.tessellate(outer_rect, rounding); + let shadow = Shape::Mesh(shadow); + Shape::Vec(vec![shadow, frame_shape]) + } + } +} + +impl Prepared { + fn paint_rect(&self) -> Rect { + let mut rect = self.content_ui.min_rect(); + rect.min -= self.frame.inner_margin.left_top(); + rect.max += self.frame.inner_margin.right_bottom(); + rect + } + + fn content_with_margin(&self) -> Rect { + let mut rect = self.content_ui.min_rect(); + rect.min -= self.frame.inner_margin.left_top() + self.frame.outer_margin.left_top(); + rect.max += self.frame.inner_margin.right_bottom() + self.frame.outer_margin.right_bottom(); + rect + } + + pub fn end(self, ui: &mut Ui) -> Response { + let paint_rect = self.paint_rect(); + + let Prepared { + frame, + where_to_put_background, + .. + } = self; + + if ui.is_rect_visible(paint_rect) { + let shape = frame.paint(paint_rect); + ui.painter().set(where_to_put_background, shape); + } + + ui.allocate_rect(self.content_with_margin(), Sense::hover()) + } +} diff --git a/nevmes-gui/crates/egui/src/containers/mod.rs b/nevmes-gui/crates/egui/src/containers/mod.rs new file mode 100644 index 0000000..53e8e7e --- /dev/null +++ b/nevmes-gui/crates/egui/src/containers/mod.rs @@ -0,0 +1,25 @@ +//! Containers are pieces of the UI which wraps other pieces of UI. Examples: [`Window`], [`ScrollArea`], [`Resize`], [`SidePanel`], etc. +//! +//! For instance, a [`Frame`] adds a frame and background to some contained UI. + +pub(crate) mod area; +pub mod collapsing_header; +mod combo_box; +pub(crate) mod frame; +pub mod panel; +pub mod popup; +pub(crate) mod resize; +pub mod scroll_area; +pub(crate) mod window; + +pub use { + area::Area, + collapsing_header::{CollapsingHeader, CollapsingResponse}, + combo_box::*, + frame::Frame, + panel::{CentralPanel, SidePanel, TopBottomPanel}, + popup::*, + resize::Resize, + scroll_area::ScrollArea, + window::Window, +}; diff --git a/nevmes-gui/crates/egui/src/containers/panel.rs b/nevmes-gui/crates/egui/src/containers/panel.rs new file mode 100644 index 0000000..e7633e2 --- /dev/null +++ b/nevmes-gui/crates/egui/src/containers/panel.rs @@ -0,0 +1,1064 @@ +//! Panels are [`Ui`] regions taking up e.g. the left side of a [`Ui`] or screen. +//! +//! Panels can either be a child of a [`Ui`] (taking up a portion of the parent) +//! or be top-level (taking up a portion of the whole screen). +//! +//! Together with [`Window`] and [`Area`]:s, top-level panels are +//! the only places where you can put you widgets. +//! +//! The order in which you add panels matter! +//! The first panel you add will always be the outermost, and the last you add will always be the innermost. +//! +//! You must never open one top-level panel from within another panel. Add one panel, then the next. +//! +//! ⚠ Always add any [`CentralPanel`] last. +//! +//! Add your [`Window`]:s after any top-level panels. + +use std::ops::RangeInclusive; + +use crate::*; + +/// State regarding panels. +#[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub struct PanelState { + pub rect: Rect, +} + +impl PanelState { + pub fn load(ctx: &Context, bar_id: Id) -> Option { + ctx.data_mut(|d| d.get_persisted(bar_id)) + } + + /// The size of the panel (from previous frame). + pub fn size(&self) -> Vec2 { + self.rect.size() + } + + fn store(self, ctx: &Context, bar_id: Id) { + ctx.data_mut(|d| d.insert_persisted(bar_id, self)); + } +} + +// ---------------------------------------------------------------------------- + +/// [`Left`](Side::Left) or [`Right`](Side::Right) +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Side { + Left, + Right, +} + +impl Side { + fn opposite(self) -> Self { + match self { + Side::Left => Self::Right, + Side::Right => Self::Left, + } + } + + fn set_rect_width(self, rect: &mut Rect, width: f32) { + match self { + Side::Left => rect.max.x = rect.min.x + width, + Side::Right => rect.min.x = rect.max.x - width, + } + } + + fn side_x(self, rect: Rect) -> f32 { + match self { + Side::Left => rect.left(), + Side::Right => rect.right(), + } + } +} + +/// A panel that covers the entire left or right side of a [`Ui`] or screen. +/// +/// The order in which you add panels matter! +/// The first panel you add will always be the outermost, and the last you add will always be the innermost. +/// +/// ⚠ Always add any [`CentralPanel`] last. +/// +/// See the [module level docs](crate::containers::panel) for more details. +/// +/// ``` +/// # egui::__run_test_ctx(|ctx| { +/// egui::SidePanel::left("my_left_panel").show(ctx, |ui| { +/// ui.label("Hello World!"); +/// }); +/// # }); +/// ``` +/// +/// See also [`TopBottomPanel`]. +#[must_use = "You should call .show()"] +pub struct SidePanel { + side: Side, + id: Id, + frame: Option, + resizable: bool, + show_separator_line: bool, + default_width: f32, + width_range: RangeInclusive, +} + +impl SidePanel { + /// The id should be globally unique, e.g. `Id::new("my_left_panel")`. + pub fn left(id: impl Into) -> Self { + Self::new(Side::Left, id) + } + + /// The id should be globally unique, e.g. `Id::new("my_right_panel")`. + pub fn right(id: impl Into) -> Self { + Self::new(Side::Right, id) + } + + /// The id should be globally unique, e.g. `Id::new("my_panel")`. + pub fn new(side: Side, id: impl Into) -> Self { + Self { + side, + id: id.into(), + frame: None, + resizable: true, + show_separator_line: true, + default_width: 200.0, + width_range: 96.0..=f32::INFINITY, + } + } + + /// Can panel be resized by dragging the edge of it? + /// + /// Default is `true`. + /// + /// If you want your panel to be resizable you also need a widget in it that + /// takes up more space as you resize it, such as: + /// * Wrapping text ([`Ui::horizontal_wrapped`]). + /// * A [`ScrollArea`]. + /// * A [`Separator`]. + /// * A [`TextEdit`]. + /// * … + pub fn resizable(mut self, resizable: bool) -> Self { + self.resizable = resizable; + self + } + + /// Show a separator line, even when not interacting with it? + /// + /// Default: `true`. + pub fn show_separator_line(mut self, show_separator_line: bool) -> Self { + self.show_separator_line = show_separator_line; + self + } + + /// The initial wrapping width of the [`SidePanel`]. + pub fn default_width(mut self, default_width: f32) -> Self { + self.default_width = default_width; + self.width_range = self.width_range.start().at_most(default_width) + ..=self.width_range.end().at_least(default_width); + self + } + + /// Minimum width of the panel. + pub fn min_width(mut self, min_width: f32) -> Self { + self.width_range = min_width..=self.width_range.end().at_least(min_width); + self + } + + /// Maximum width of the panel. + pub fn max_width(mut self, max_width: f32) -> Self { + self.width_range = self.width_range.start().at_most(max_width)..=max_width; + self + } + + /// The allowable width range for the panel. + pub fn width_range(mut self, width_range: RangeInclusive) -> Self { + self.default_width = clamp_to_range(self.default_width, width_range.clone()); + self.width_range = width_range; + self + } + + /// Enforce this exact width. + pub fn exact_width(mut self, width: f32) -> Self { + self.default_width = width; + self.width_range = width..=width; + self + } + + /// Change the background color, margins, etc. + pub fn frame(mut self, frame: Frame) -> Self { + self.frame = Some(frame); + self + } +} + +impl SidePanel { + /// Show the panel inside a [`Ui`]. + pub fn show_inside( + self, + ui: &mut Ui, + add_contents: impl FnOnce(&mut Ui) -> R, + ) -> InnerResponse { + self.show_inside_dyn(ui, Box::new(add_contents)) + } + + /// Show the panel inside a [`Ui`]. + fn show_inside_dyn<'c, R>( + self, + ui: &mut Ui, + add_contents: Box R + 'c>, + ) -> InnerResponse { + let Self { + side, + id, + frame, + resizable, + show_separator_line, + default_width, + width_range, + } = self; + + let available_rect = ui.available_rect_before_wrap(); + let mut panel_rect = available_rect; + { + let mut width = default_width; + if let Some(state) = PanelState::load(ui.ctx(), id) { + width = state.rect.width(); + } + width = clamp_to_range(width, width_range.clone()).at_most(available_rect.width()); + side.set_rect_width(&mut panel_rect, width); + ui.ctx().check_for_id_clash(id, panel_rect, "SidePanel"); + } + + let mut resize_hover = false; + let mut is_resizing = false; + if resizable { + let resize_id = id.with("__resize"); + if let Some(pointer) = ui.ctx().pointer_latest_pos() { + let we_are_on_top = ui + .ctx() + .layer_id_at(pointer) + .map_or(true, |top_layer_id| top_layer_id == ui.layer_id()); + + let resize_x = side.opposite().side_x(panel_rect); + let mouse_over_resize_line = we_are_on_top + && panel_rect.y_range().contains(&pointer.y) + && (resize_x - pointer.x).abs() + <= ui.style().interaction.resize_grab_radius_side; + + if ui.input(|i| i.pointer.any_pressed() && i.pointer.any_down()) + && mouse_over_resize_line + { + ui.memory_mut(|mem| mem.set_dragged_id(resize_id)); + } + is_resizing = ui.memory(|mem| mem.is_being_dragged(resize_id)); + if is_resizing { + let width = (pointer.x - side.side_x(panel_rect)).abs(); + let width = + clamp_to_range(width, width_range.clone()).at_most(available_rect.width()); + side.set_rect_width(&mut panel_rect, width); + } + + let dragging_something_else = + ui.input(|i| i.pointer.any_down() || i.pointer.any_pressed()); + resize_hover = mouse_over_resize_line && !dragging_something_else; + + if resize_hover || is_resizing { + ui.ctx().set_cursor_icon(CursorIcon::ResizeHorizontal); + } + } + } + + let mut panel_ui = ui.child_ui_with_id_source(panel_rect, Layout::top_down(Align::Min), id); + panel_ui.expand_to_include_rect(panel_rect); + let frame = frame.unwrap_or_else(|| Frame::side_top_panel(ui.style())); + let inner_response = frame.show(&mut panel_ui, |ui| { + ui.set_min_height(ui.max_rect().height()); // Make sure the frame fills the full height + ui.set_min_width(*width_range.start()); + add_contents(ui) + }); + + let rect = inner_response.response.rect; + + { + let mut cursor = ui.cursor(); + match side { + Side::Left => { + cursor.min.x = rect.max.x; + } + Side::Right => { + cursor.max.x = rect.min.x; + } + } + ui.set_cursor(cursor); + } + ui.expand_to_include_rect(rect); + + PanelState { rect }.store(ui.ctx(), id); + + { + let stroke = if is_resizing { + ui.style().visuals.widgets.active.fg_stroke // highly visible + } else if resize_hover { + ui.style().visuals.widgets.hovered.fg_stroke // highly visible + } else if show_separator_line { + // TOOD(emilk): distinguish resizable from non-resizable + ui.style().visuals.widgets.noninteractive.bg_stroke // dim + } else { + Stroke::NONE + }; + // TODO(emilk): draw line on top of all panels in this ui when https://github.com/emilk/egui/issues/1516 is done + // In the meantime: nudge the line so its inside the panel, so it won't be covered by neighboring panel + // (hence the shrink). + let resize_x = side.opposite().side_x(rect.shrink(1.0)); + let resize_x = ui.painter().round_to_pixel(resize_x); + ui.painter().vline(resize_x, rect.y_range(), stroke); + } + + inner_response + } + + /// Show the panel at the top level. + pub fn show( + self, + ctx: &Context, + add_contents: impl FnOnce(&mut Ui) -> R, + ) -> InnerResponse { + self.show_dyn(ctx, Box::new(add_contents)) + } + + /// Show the panel at the top level. + fn show_dyn<'c, R>( + self, + ctx: &Context, + add_contents: Box R + 'c>, + ) -> InnerResponse { + let layer_id = LayerId::background(); + let side = self.side; + let available_rect = ctx.available_rect(); + let clip_rect = ctx.screen_rect(); + let mut panel_ui = Ui::new(ctx.clone(), layer_id, self.id, available_rect, clip_rect); + + let inner_response = self.show_inside_dyn(&mut panel_ui, add_contents); + let rect = inner_response.response.rect; + + match side { + Side::Left => ctx.frame_state_mut(|state| { + state.allocate_left_panel(Rect::from_min_max(available_rect.min, rect.max)); + }), + Side::Right => ctx.frame_state_mut(|state| { + state.allocate_right_panel(Rect::from_min_max(rect.min, available_rect.max)); + }), + } + inner_response + } + + /// Show the panel if `is_expanded` is `true`, + /// otherwise don't show it, but with a nice animation between collapsed and expanded. + pub fn show_animated( + self, + ctx: &Context, + is_expanded: bool, + add_contents: impl FnOnce(&mut Ui) -> R, + ) -> Option> { + let how_expanded = ctx.animate_bool(self.id.with("animation"), is_expanded); + + if 0.0 == how_expanded { + None + } else if how_expanded < 1.0 { + // Show a fake panel in this in-between animation state: + // TODO(emilk): move the panel out-of-screen instead of changing its width. + // Then we can actually paint it as it animates. + let expanded_width = PanelState::load(ctx, self.id) + .map_or(self.default_width, |state| state.rect.width()); + let fake_width = how_expanded * expanded_width; + Self { + id: self.id.with("animating_panel"), + ..self + } + .resizable(false) + .exact_width(fake_width) + .show(ctx, |_ui| {}); + None + } else { + // Show the real panel: + Some(self.show(ctx, add_contents)) + } + } + + /// Show the panel if `is_expanded` is `true`, + /// otherwise don't show it, but with a nice animation between collapsed and expanded. + pub fn show_animated_inside( + self, + ui: &mut Ui, + is_expanded: bool, + add_contents: impl FnOnce(&mut Ui) -> R, + ) -> Option> { + let how_expanded = ui + .ctx() + .animate_bool(self.id.with("animation"), is_expanded); + + if 0.0 == how_expanded { + None + } else if how_expanded < 1.0 { + // Show a fake panel in this in-between animation state: + // TODO(emilk): move the panel out-of-screen instead of changing its width. + // Then we can actually paint it as it animates. + let expanded_width = PanelState::load(ui.ctx(), self.id) + .map_or(self.default_width, |state| state.rect.width()); + let fake_width = how_expanded * expanded_width; + Self { + id: self.id.with("animating_panel"), + ..self + } + .resizable(false) + .exact_width(fake_width) + .show_inside(ui, |_ui| {}); + None + } else { + // Show the real panel: + Some(self.show_inside(ui, add_contents)) + } + } + + /// Show either a collapsed or a expanded panel, with a nice animation between. + pub fn show_animated_between( + ctx: &Context, + is_expanded: bool, + collapsed_panel: Self, + expanded_panel: Self, + add_contents: impl FnOnce(&mut Ui, f32) -> R, + ) -> Option> { + let how_expanded = ctx.animate_bool(expanded_panel.id.with("animation"), is_expanded); + + if 0.0 == how_expanded { + Some(collapsed_panel.show(ctx, |ui| add_contents(ui, how_expanded))) + } else if how_expanded < 1.0 { + // Show animation: + let collapsed_width = PanelState::load(ctx, collapsed_panel.id) + .map_or(collapsed_panel.default_width, |state| state.rect.width()); + let expanded_width = PanelState::load(ctx, expanded_panel.id) + .map_or(expanded_panel.default_width, |state| state.rect.width()); + let fake_width = lerp(collapsed_width..=expanded_width, how_expanded); + Self { + id: expanded_panel.id.with("animating_panel"), + ..expanded_panel + } + .resizable(false) + .exact_width(fake_width) + .show(ctx, |ui| add_contents(ui, how_expanded)); + None + } else { + Some(expanded_panel.show(ctx, |ui| add_contents(ui, how_expanded))) + } + } + + /// Show either a collapsed or a expanded panel, with a nice animation between. + pub fn show_animated_between_inside( + ui: &mut Ui, + is_expanded: bool, + collapsed_panel: Self, + expanded_panel: Self, + add_contents: impl FnOnce(&mut Ui, f32) -> R, + ) -> InnerResponse { + let how_expanded = ui + .ctx() + .animate_bool(expanded_panel.id.with("animation"), is_expanded); + + if 0.0 == how_expanded { + collapsed_panel.show_inside(ui, |ui| add_contents(ui, how_expanded)) + } else if how_expanded < 1.0 { + // Show animation: + let collapsed_width = PanelState::load(ui.ctx(), collapsed_panel.id) + .map_or(collapsed_panel.default_width, |state| state.rect.width()); + let expanded_width = PanelState::load(ui.ctx(), expanded_panel.id) + .map_or(expanded_panel.default_width, |state| state.rect.width()); + let fake_width = lerp(collapsed_width..=expanded_width, how_expanded); + Self { + id: expanded_panel.id.with("animating_panel"), + ..expanded_panel + } + .resizable(false) + .exact_width(fake_width) + .show_inside(ui, |ui| add_contents(ui, how_expanded)) + } else { + expanded_panel.show_inside(ui, |ui| add_contents(ui, how_expanded)) + } + } +} + +// ---------------------------------------------------------------------------- + +/// [`Top`](TopBottomSide::Top) or [`Bottom`](TopBottomSide::Bottom) +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum TopBottomSide { + Top, + Bottom, +} + +impl TopBottomSide { + fn opposite(self) -> Self { + match self { + TopBottomSide::Top => Self::Bottom, + TopBottomSide::Bottom => Self::Top, + } + } + + fn set_rect_height(self, rect: &mut Rect, height: f32) { + match self { + TopBottomSide::Top => rect.max.y = rect.min.y + height, + TopBottomSide::Bottom => rect.min.y = rect.max.y - height, + } + } + + fn side_y(self, rect: Rect) -> f32 { + match self { + TopBottomSide::Top => rect.top(), + TopBottomSide::Bottom => rect.bottom(), + } + } +} + +/// A panel that covers the entire top or bottom of a [`Ui`] or screen. +/// +/// The order in which you add panels matter! +/// The first panel you add will always be the outermost, and the last you add will always be the innermost. +/// +/// ⚠ Always add any [`CentralPanel`] last. +/// +/// See the [module level docs](crate::containers::panel) for more details. +/// +/// ``` +/// # egui::__run_test_ctx(|ctx| { +/// egui::TopBottomPanel::top("my_panel").show(ctx, |ui| { +/// ui.label("Hello World!"); +/// }); +/// # }); +/// ``` +/// +/// See also [`SidePanel`]. +#[must_use = "You should call .show()"] +pub struct TopBottomPanel { + side: TopBottomSide, + id: Id, + frame: Option, + resizable: bool, + show_separator_line: bool, + default_height: Option, + height_range: RangeInclusive, +} + +impl TopBottomPanel { + /// The id should be globally unique, e.g. `Id::new("my_top_panel")`. + pub fn top(id: impl Into) -> Self { + Self::new(TopBottomSide::Top, id) + } + + /// The id should be globally unique, e.g. `Id::new("my_bottom_panel")`. + pub fn bottom(id: impl Into) -> Self { + Self::new(TopBottomSide::Bottom, id) + } + + /// The id should be globally unique, e.g. `Id::new("my_panel")`. + pub fn new(side: TopBottomSide, id: impl Into) -> Self { + Self { + side, + id: id.into(), + frame: None, + resizable: false, + show_separator_line: true, + default_height: None, + height_range: 20.0..=f32::INFINITY, + } + } + + /// Can panel be resized by dragging the edge of it? + /// + /// Default is `false`. + /// + /// If you want your panel to be resizable you also need a widget in it that + /// takes up more space as you resize it, such as: + /// * Wrapping text ([`Ui::horizontal_wrapped`]). + /// * A [`ScrollArea`]. + /// * A [`Separator`]. + /// * A [`TextEdit`]. + /// * … + pub fn resizable(mut self, resizable: bool) -> Self { + self.resizable = resizable; + self + } + + /// Show a separator line, even when not interacting with it? + /// + /// Default: `true`. + pub fn show_separator_line(mut self, show_separator_line: bool) -> Self { + self.show_separator_line = show_separator_line; + self + } + + /// The initial height of the [`SidePanel`]. + /// Defaults to [`style::Spacing::interact_size`].y. + pub fn default_height(mut self, default_height: f32) -> Self { + self.default_height = Some(default_height); + self.height_range = self.height_range.start().at_most(default_height) + ..=self.height_range.end().at_least(default_height); + self + } + + /// Minimum height of the panel. + pub fn min_height(mut self, min_height: f32) -> Self { + self.height_range = min_height..=self.height_range.end().at_least(min_height); + self + } + + /// Maximum height of the panel. + pub fn max_height(mut self, max_height: f32) -> Self { + self.height_range = self.height_range.start().at_most(max_height)..=max_height; + self + } + + /// The allowable height range for the panel. + pub fn height_range(mut self, height_range: RangeInclusive) -> Self { + self.default_height = self + .default_height + .map(|default_height| clamp_to_range(default_height, height_range.clone())); + self.height_range = height_range; + self + } + + /// Enforce this exact height. + pub fn exact_height(mut self, height: f32) -> Self { + self.default_height = Some(height); + self.height_range = height..=height; + self + } + + /// Change the background color, margins, etc. + pub fn frame(mut self, frame: Frame) -> Self { + self.frame = Some(frame); + self + } +} + +impl TopBottomPanel { + /// Show the panel inside a [`Ui`]. + pub fn show_inside( + self, + ui: &mut Ui, + add_contents: impl FnOnce(&mut Ui) -> R, + ) -> InnerResponse { + self.show_inside_dyn(ui, Box::new(add_contents)) + } + + /// Show the panel inside a [`Ui`]. + fn show_inside_dyn<'c, R>( + self, + ui: &mut Ui, + add_contents: Box R + 'c>, + ) -> InnerResponse { + let Self { + side, + id, + frame, + resizable, + show_separator_line, + default_height, + height_range, + } = self; + + let available_rect = ui.available_rect_before_wrap(); + let mut panel_rect = available_rect; + { + let mut height = if let Some(state) = PanelState::load(ui.ctx(), id) { + state.rect.height() + } else { + default_height.unwrap_or_else(|| ui.style().spacing.interact_size.y) + }; + height = clamp_to_range(height, height_range.clone()).at_most(available_rect.height()); + side.set_rect_height(&mut panel_rect, height); + ui.ctx() + .check_for_id_clash(id, panel_rect, "TopBottomPanel"); + } + + let mut resize_hover = false; + let mut is_resizing = false; + if resizable { + let resize_id = id.with("__resize"); + let latest_pos = ui.input(|i| i.pointer.latest_pos()); + if let Some(pointer) = latest_pos { + let we_are_on_top = ui + .ctx() + .layer_id_at(pointer) + .map_or(true, |top_layer_id| top_layer_id == ui.layer_id()); + + let resize_y = side.opposite().side_y(panel_rect); + let mouse_over_resize_line = we_are_on_top + && panel_rect.x_range().contains(&pointer.x) + && (resize_y - pointer.y).abs() + <= ui.style().interaction.resize_grab_radius_side; + + if ui.input(|i| i.pointer.any_pressed() && i.pointer.any_down()) + && mouse_over_resize_line + { + ui.memory_mut(|mem| mem.interaction.drag_id = Some(resize_id)); + } + is_resizing = ui.memory(|mem| mem.interaction.drag_id == Some(resize_id)); + if is_resizing { + let height = (pointer.y - side.side_y(panel_rect)).abs(); + let height = clamp_to_range(height, height_range.clone()) + .at_most(available_rect.height()); + side.set_rect_height(&mut panel_rect, height); + } + + let dragging_something_else = + ui.input(|i| i.pointer.any_down() || i.pointer.any_pressed()); + resize_hover = mouse_over_resize_line && !dragging_something_else; + + if resize_hover || is_resizing { + ui.ctx().set_cursor_icon(CursorIcon::ResizeVertical); + } + } + } + + let mut panel_ui = ui.child_ui_with_id_source(panel_rect, Layout::top_down(Align::Min), id); + panel_ui.expand_to_include_rect(panel_rect); + let frame = frame.unwrap_or_else(|| Frame::side_top_panel(ui.style())); + let inner_response = frame.show(&mut panel_ui, |ui| { + ui.set_min_width(ui.max_rect().width()); // Make the frame fill full width + ui.set_min_height(*height_range.start()); + add_contents(ui) + }); + + let rect = inner_response.response.rect; + + { + let mut cursor = ui.cursor(); + match side { + TopBottomSide::Top => { + cursor.min.y = rect.max.y; + } + TopBottomSide::Bottom => { + cursor.max.y = rect.min.y; + } + } + ui.set_cursor(cursor); + } + ui.expand_to_include_rect(rect); + + PanelState { rect }.store(ui.ctx(), id); + + { + let stroke = if is_resizing { + ui.style().visuals.widgets.active.fg_stroke // highly visible + } else if resize_hover { + ui.style().visuals.widgets.hovered.fg_stroke // highly visible + } else if show_separator_line { + // TOOD(emilk): distinguish resizable from non-resizable + ui.style().visuals.widgets.noninteractive.bg_stroke // dim + } else { + Stroke::NONE + }; + // TODO(emilk): draw line on top of all panels in this ui when https://github.com/emilk/egui/issues/1516 is done + // In the meantime: nudge the line so its inside the panel, so it won't be covered by neighboring panel + // (hence the shrink). + let resize_y = side.opposite().side_y(rect.shrink(1.0)); + let resize_y = ui.painter().round_to_pixel(resize_y); + ui.painter().hline(rect.x_range(), resize_y, stroke); + } + + inner_response + } + + /// Show the panel at the top level. + pub fn show( + self, + ctx: &Context, + add_contents: impl FnOnce(&mut Ui) -> R, + ) -> InnerResponse { + self.show_dyn(ctx, Box::new(add_contents)) + } + + /// Show the panel at the top level. + fn show_dyn<'c, R>( + self, + ctx: &Context, + add_contents: Box R + 'c>, + ) -> InnerResponse { + let layer_id = LayerId::background(); + let available_rect = ctx.available_rect(); + let side = self.side; + + let clip_rect = ctx.screen_rect(); + let mut panel_ui = Ui::new(ctx.clone(), layer_id, self.id, available_rect, clip_rect); + + let inner_response = self.show_inside_dyn(&mut panel_ui, add_contents); + let rect = inner_response.response.rect; + + match side { + TopBottomSide::Top => { + ctx.frame_state_mut(|state| { + state.allocate_top_panel(Rect::from_min_max(available_rect.min, rect.max)); + }); + } + TopBottomSide::Bottom => { + ctx.frame_state_mut(|state| { + state.allocate_bottom_panel(Rect::from_min_max(rect.min, available_rect.max)); + }); + } + } + + inner_response + } + + /// Show the panel if `is_expanded` is `true`, + /// otherwise don't show it, but with a nice animation between collapsed and expanded. + pub fn show_animated( + self, + ctx: &Context, + is_expanded: bool, + add_contents: impl FnOnce(&mut Ui) -> R, + ) -> Option> { + let how_expanded = ctx.animate_bool(self.id.with("animation"), is_expanded); + + if 0.0 == how_expanded { + None + } else if how_expanded < 1.0 { + // Show a fake panel in this in-between animation state: + // TODO(emilk): move the panel out-of-screen instead of changing its height. + // Then we can actually paint it as it animates. + let expanded_height = PanelState::load(ctx, self.id) + .map(|state| state.rect.height()) + .or(self.default_height) + .unwrap_or_else(|| ctx.style().spacing.interact_size.y); + let fake_height = how_expanded * expanded_height; + Self { + id: self.id.with("animating_panel"), + ..self + } + .resizable(false) + .exact_height(fake_height) + .show(ctx, |_ui| {}); + None + } else { + // Show the real panel: + Some(self.show(ctx, add_contents)) + } + } + + /// Show the panel if `is_expanded` is `true`, + /// otherwise don't show it, but with a nice animation between collapsed and expanded. + pub fn show_animated_inside( + self, + ui: &mut Ui, + is_expanded: bool, + add_contents: impl FnOnce(&mut Ui) -> R, + ) -> Option> { + let how_expanded = ui + .ctx() + .animate_bool(self.id.with("animation"), is_expanded); + + if 0.0 == how_expanded { + None + } else if how_expanded < 1.0 { + // Show a fake panel in this in-between animation state: + // TODO(emilk): move the panel out-of-screen instead of changing its height. + // Then we can actually paint it as it animates. + let expanded_height = PanelState::load(ui.ctx(), self.id) + .map(|state| state.rect.height()) + .or(self.default_height) + .unwrap_or_else(|| ui.style().spacing.interact_size.y); + let fake_height = how_expanded * expanded_height; + Self { + id: self.id.with("animating_panel"), + ..self + } + .resizable(false) + .exact_height(fake_height) + .show_inside(ui, |_ui| {}); + None + } else { + // Show the real panel: + Some(self.show_inside(ui, add_contents)) + } + } + + /// Show either a collapsed or a expanded panel, with a nice animation between. + pub fn show_animated_between( + ctx: &Context, + is_expanded: bool, + collapsed_panel: Self, + expanded_panel: Self, + add_contents: impl FnOnce(&mut Ui, f32) -> R, + ) -> Option> { + let how_expanded = ctx.animate_bool(expanded_panel.id.with("animation"), is_expanded); + + if 0.0 == how_expanded { + Some(collapsed_panel.show(ctx, |ui| add_contents(ui, how_expanded))) + } else if how_expanded < 1.0 { + // Show animation: + let collapsed_height = PanelState::load(ctx, collapsed_panel.id) + .map(|state| state.rect.height()) + .or(collapsed_panel.default_height) + .unwrap_or_else(|| ctx.style().spacing.interact_size.y); + + let expanded_height = PanelState::load(ctx, expanded_panel.id) + .map(|state| state.rect.height()) + .or(expanded_panel.default_height) + .unwrap_or_else(|| ctx.style().spacing.interact_size.y); + + let fake_height = lerp(collapsed_height..=expanded_height, how_expanded); + Self { + id: expanded_panel.id.with("animating_panel"), + ..expanded_panel + } + .resizable(false) + .exact_height(fake_height) + .show(ctx, |ui| add_contents(ui, how_expanded)); + None + } else { + Some(expanded_panel.show(ctx, |ui| add_contents(ui, how_expanded))) + } + } + + /// Show either a collapsed or a expanded panel, with a nice animation between. + pub fn show_animated_between_inside( + ui: &mut Ui, + is_expanded: bool, + collapsed_panel: Self, + expanded_panel: Self, + add_contents: impl FnOnce(&mut Ui, f32) -> R, + ) -> InnerResponse { + let how_expanded = ui + .ctx() + .animate_bool(expanded_panel.id.with("animation"), is_expanded); + + if 0.0 == how_expanded { + collapsed_panel.show_inside(ui, |ui| add_contents(ui, how_expanded)) + } else if how_expanded < 1.0 { + // Show animation: + let collapsed_height = PanelState::load(ui.ctx(), collapsed_panel.id) + .map(|state| state.rect.height()) + .or(collapsed_panel.default_height) + .unwrap_or_else(|| ui.style().spacing.interact_size.y); + + let expanded_height = PanelState::load(ui.ctx(), expanded_panel.id) + .map(|state| state.rect.height()) + .or(expanded_panel.default_height) + .unwrap_or_else(|| ui.style().spacing.interact_size.y); + + let fake_height = lerp(collapsed_height..=expanded_height, how_expanded); + Self { + id: expanded_panel.id.with("animating_panel"), + ..expanded_panel + } + .resizable(false) + .exact_height(fake_height) + .show_inside(ui, |ui| add_contents(ui, how_expanded)) + } else { + expanded_panel.show_inside(ui, |ui| add_contents(ui, how_expanded)) + } + } +} + +// ---------------------------------------------------------------------------- + +/// A panel that covers the remainder of the screen, +/// i.e. whatever area is left after adding other panels. +/// +/// The order in which you add panels matter! +/// The first panel you add will always be the outermost, and the last you add will always be the innermost. +/// +/// ⚠ [`CentralPanel`] must be added after all other panels! +/// +/// NOTE: Any [`Window`]s and [`Area`]s will cover the top-level [`CentralPanel`]. +/// +/// See the [module level docs](crate::containers::panel) for more details. +/// +/// ``` +/// # egui::__run_test_ctx(|ctx| { +/// egui::TopBottomPanel::top("my_panel").show(ctx, |ui| { +/// ui.label("Hello World! From `TopBottomPanel`, that must be before `CentralPanel`!"); +/// }); +/// egui::CentralPanel::default().show(ctx, |ui| { +/// ui.label("Hello World!"); +/// }); +/// # }); +/// ``` +#[must_use = "You should call .show()"] +#[derive(Default)] +pub struct CentralPanel { + frame: Option, +} + +impl CentralPanel { + /// Change the background color, margins, etc. + pub fn frame(mut self, frame: Frame) -> Self { + self.frame = Some(frame); + self + } +} + +impl CentralPanel { + /// Show the panel inside a [`Ui`]. + pub fn show_inside( + self, + ui: &mut Ui, + add_contents: impl FnOnce(&mut Ui) -> R, + ) -> InnerResponse { + self.show_inside_dyn(ui, Box::new(add_contents)) + } + + /// Show the panel inside a [`Ui`]. + fn show_inside_dyn<'c, R>( + self, + ui: &mut Ui, + add_contents: Box R + 'c>, + ) -> InnerResponse { + let Self { frame } = self; + + let panel_rect = ui.available_rect_before_wrap(); + let mut panel_ui = ui.child_ui(panel_rect, Layout::top_down(Align::Min)); + + let frame = frame.unwrap_or_else(|| Frame::central_panel(ui.style())); + frame.show(&mut panel_ui, |ui| { + ui.expand_to_include_rect(ui.max_rect()); // Expand frame to include it all + add_contents(ui) + }) + } + + /// Show the panel at the top level. + pub fn show( + self, + ctx: &Context, + add_contents: impl FnOnce(&mut Ui) -> R, + ) -> InnerResponse { + self.show_dyn(ctx, Box::new(add_contents)) + } + + /// Show the panel at the top level. + fn show_dyn<'c, R>( + self, + ctx: &Context, + add_contents: Box R + 'c>, + ) -> InnerResponse { + let available_rect = ctx.available_rect(); + let layer_id = LayerId::background(); + let id = Id::new("central_panel"); + + let clip_rect = ctx.screen_rect(); + let mut panel_ui = Ui::new(ctx.clone(), layer_id, id, available_rect, clip_rect); + + let inner_response = self.show_inside_dyn(&mut panel_ui, add_contents); + + // Only inform ctx about what we actually used, so we can shrink the native window to fit. + ctx.frame_state_mut(|state| state.allocate_central_panel(inner_response.response.rect)); + + inner_response + } +} + +fn clamp_to_range(x: f32, range: RangeInclusive) -> f32 { + x.clamp( + range.start().min(*range.end()), + range.start().max(*range.end()), + ) +} diff --git a/nevmes-gui/crates/egui/src/containers/popup.rs b/nevmes-gui/crates/egui/src/containers/popup.rs new file mode 100644 index 0000000..e4ad1b1 --- /dev/null +++ b/nevmes-gui/crates/egui/src/containers/popup.rs @@ -0,0 +1,378 @@ +//! Show popup windows, tooltips, context menus etc. + +use crate::*; + +// ---------------------------------------------------------------------------- + +/// Same state for all tooltips. +#[derive(Clone, Debug, Default)] +pub(crate) struct TooltipState { + last_common_id: Option, + individual_ids_and_sizes: ahash::HashMap, +} + +impl TooltipState { + pub fn load(ctx: &Context) -> Option { + ctx.data_mut(|d| d.get_temp(Id::null())) + } + + fn store(self, ctx: &Context) { + ctx.data_mut(|d| d.insert_temp(Id::null(), self)); + } + + fn individual_tooltip_size(&self, common_id: Id, index: usize) -> Option { + if self.last_common_id == Some(common_id) { + Some(self.individual_ids_and_sizes.get(&index)?.1) + } else { + None + } + } + + fn set_individual_tooltip( + &mut self, + common_id: Id, + index: usize, + individual_id: Id, + size: Vec2, + ) { + if self.last_common_id != Some(common_id) { + self.last_common_id = Some(common_id); + self.individual_ids_and_sizes.clear(); + } + + self.individual_ids_and_sizes + .insert(index, (individual_id, size)); + } +} + +// ---------------------------------------------------------------------------- + +/// Show a tooltip at the current pointer position (if any). +/// +/// Most of the time it is easier to use [`Response::on_hover_ui`]. +/// +/// See also [`show_tooltip_text`]. +/// +/// Returns `None` if the tooltip could not be placed. +/// +/// ``` +/// # egui::__run_test_ui(|ui| { +/// if ui.ui_contains_pointer() { +/// egui::show_tooltip(ui.ctx(), egui::Id::new("my_tooltip"), |ui| { +/// ui.label("Helpful text"); +/// }); +/// } +/// # }); +/// ``` +pub fn show_tooltip( + ctx: &Context, + id: Id, + add_contents: impl FnOnce(&mut Ui) -> R, +) -> Option { + show_tooltip_at_pointer(ctx, id, add_contents) +} + +/// Show a tooltip at the current pointer position (if any). +/// +/// Most of the time it is easier to use [`Response::on_hover_ui`]. +/// +/// See also [`show_tooltip_text`]. +/// +/// Returns `None` if the tooltip could not be placed. +/// +/// ``` +/// # egui::__run_test_ui(|ui| { +/// if ui.ui_contains_pointer() { +/// egui::show_tooltip_at_pointer(ui.ctx(), egui::Id::new("my_tooltip"), |ui| { +/// ui.label("Helpful text"); +/// }); +/// } +/// # }); +/// ``` +pub fn show_tooltip_at_pointer( + ctx: &Context, + id: Id, + add_contents: impl FnOnce(&mut Ui) -> R, +) -> Option { + let suggested_pos = ctx + .input(|i| i.pointer.hover_pos()) + .map(|pointer_pos| pointer_pos + vec2(16.0, 16.0)); + show_tooltip_at(ctx, id, suggested_pos, add_contents) +} + +/// Show a tooltip under the given area. +/// +/// If the tooltip does not fit under the area, it tries to place it above it instead. +pub fn show_tooltip_for( + ctx: &Context, + id: Id, + rect: &Rect, + add_contents: impl FnOnce(&mut Ui) -> R, +) -> Option { + let expanded_rect = rect.expand2(vec2(2.0, 4.0)); + let (above, position) = if ctx.input(|i| i.any_touches()) { + (true, expanded_rect.left_top()) + } else { + (false, expanded_rect.left_bottom()) + }; + show_tooltip_at_avoid_dyn( + ctx, + id, + Some(position), + above, + expanded_rect, + Box::new(add_contents), + ) +} + +/// Show a tooltip at the given position. +/// +/// Returns `None` if the tooltip could not be placed. +pub fn show_tooltip_at( + ctx: &Context, + id: Id, + suggested_position: Option, + add_contents: impl FnOnce(&mut Ui) -> R, +) -> Option { + let above = false; + show_tooltip_at_avoid_dyn( + ctx, + id, + suggested_position, + above, + Rect::NOTHING, + Box::new(add_contents), + ) +} + +fn show_tooltip_at_avoid_dyn<'c, R>( + ctx: &Context, + individual_id: Id, + suggested_position: Option, + above: bool, + mut avoid_rect: Rect, + add_contents: Box R + 'c>, +) -> Option { + let spacing = 4.0; + + // if there are multiple tooltips open they should use the same common_id for the `tooltip_size` caching to work. + let mut frame_state = + ctx.frame_state(|fs| fs.tooltip_state) + .unwrap_or(crate::frame_state::TooltipFrameState { + common_id: individual_id, + rect: Rect::NOTHING, + count: 0, + }); + + let mut position = if frame_state.rect.is_positive() { + avoid_rect = avoid_rect.union(frame_state.rect); + if above { + frame_state.rect.left_top() - spacing * Vec2::Y + } else { + frame_state.rect.left_bottom() + spacing * Vec2::Y + } + } else if let Some(position) = suggested_position { + position + } else if ctx.memory(|mem| mem.everything_is_visible()) { + Pos2::ZERO + } else { + return None; // No good place for a tooltip :( + }; + + let mut long_state = TooltipState::load(ctx).unwrap_or_default(); + let expected_size = + long_state.individual_tooltip_size(frame_state.common_id, frame_state.count); + let expected_size = expected_size.unwrap_or_else(|| vec2(64.0, 32.0)); + + if above { + position.y -= expected_size.y; + } + + position = position.at_most(ctx.screen_rect().max - expected_size); + + // check if we intersect the avoid_rect + { + let new_rect = Rect::from_min_size(position, expected_size); + + // Note: We use shrink so that we don't get false positives when the rects just touch + if new_rect.shrink(1.0).intersects(avoid_rect) { + if above { + // place below instead: + position = avoid_rect.left_bottom() + spacing * Vec2::Y; + } else { + // place above instead: + position = Pos2::new(position.x, avoid_rect.min.y - expected_size.y - spacing); + } + } + } + + let position = position.at_least(ctx.screen_rect().min); + + let area_id = frame_state.common_id.with(frame_state.count); + + let InnerResponse { inner, response } = + show_tooltip_area_dyn(ctx, area_id, position, add_contents); + + long_state.set_individual_tooltip( + frame_state.common_id, + frame_state.count, + individual_id, + response.rect.size(), + ); + long_state.store(ctx); + + frame_state.count += 1; + frame_state.rect = frame_state.rect.union(response.rect); + ctx.frame_state_mut(|fs| fs.tooltip_state = Some(frame_state)); + + Some(inner) +} + +/// Show some text at the current pointer position (if any). +/// +/// Most of the time it is easier to use [`Response::on_hover_text`]. +/// +/// See also [`show_tooltip`]. +/// +/// Returns `None` if the tooltip could not be placed. +/// +/// ``` +/// # egui::__run_test_ui(|ui| { +/// if ui.ui_contains_pointer() { +/// egui::show_tooltip_text(ui.ctx(), egui::Id::new("my_tooltip"), "Helpful text"); +/// } +/// # }); +/// ``` +pub fn show_tooltip_text(ctx: &Context, id: Id, text: impl Into) -> Option<()> { + show_tooltip(ctx, id, |ui| { + crate::widgets::Label::new(text).ui(ui); + }) +} + +/// Show a pop-over window. +fn show_tooltip_area_dyn<'c, R>( + ctx: &Context, + area_id: Id, + window_pos: Pos2, + add_contents: Box R + 'c>, +) -> InnerResponse { + use containers::*; + Area::new(area_id) + .order(Order::Tooltip) + .fixed_pos(window_pos) + .constrain(true) + .interactable(false) + .drag_bounds(ctx.screen_rect()) + .show(ctx, |ui| { + Frame::popup(&ctx.style()) + .show(ui, |ui| { + ui.set_max_width(ui.spacing().tooltip_width); + add_contents(ui) + }) + .inner + }) +} + +/// Was this popup visible last frame? +pub fn was_tooltip_open_last_frame(ctx: &Context, tooltip_id: Id) -> bool { + if let Some(state) = TooltipState::load(ctx) { + if let Some(common_id) = state.last_common_id { + for (count, (individual_id, _size)) in &state.individual_ids_and_sizes { + if *individual_id == tooltip_id { + let area_id = common_id.with(count); + let layer_id = LayerId::new(Order::Tooltip, area_id); + if ctx.memory(|mem| mem.areas.visible_last_frame(&layer_id)) { + return true; + } + } + } + } + } + + false +} + +/// Helper for [`popup_above_or_below_widget`]. +pub fn popup_below_widget( + ui: &Ui, + popup_id: Id, + widget_response: &Response, + add_contents: impl FnOnce(&mut Ui) -> R, +) -> Option { + popup_above_or_below_widget( + ui, + popup_id, + widget_response, + AboveOrBelow::Below, + add_contents, + ) +} + +/// Shows a popup above or below another widget. +/// +/// Useful for drop-down menus (combo boxes) or suggestion menus under text fields. +/// +/// The opened popup will have the same width as the parent. +/// +/// You must open the popup with [`Memory::open_popup`] or [`Memory::toggle_popup`]. +/// +/// Returns `None` if the popup is not open. +/// +/// ``` +/// # egui::__run_test_ui(|ui| { +/// let response = ui.button("Open popup"); +/// let popup_id = ui.make_persistent_id("my_unique_id"); +/// if response.clicked() { +/// ui.memory_mut(|mem| mem.toggle_popup(popup_id)); +/// } +/// let below = egui::AboveOrBelow::Below; +/// egui::popup::popup_above_or_below_widget(ui, popup_id, &response, below, |ui| { +/// ui.set_min_width(200.0); // if you want to control the size +/// ui.label("Some more info, or things you can select:"); +/// ui.label("…"); +/// }); +/// # }); +/// ``` +pub fn popup_above_or_below_widget( + ui: &Ui, + popup_id: Id, + widget_response: &Response, + above_or_below: AboveOrBelow, + add_contents: impl FnOnce(&mut Ui) -> R, +) -> Option { + if ui.memory(|mem| mem.is_popup_open(popup_id)) { + let (pos, pivot) = match above_or_below { + AboveOrBelow::Above => (widget_response.rect.left_top(), Align2::LEFT_BOTTOM), + AboveOrBelow::Below => (widget_response.rect.left_bottom(), Align2::LEFT_TOP), + }; + + let inner = Area::new(popup_id) + .order(Order::Foreground) + .constrain(true) + .fixed_pos(pos) + .pivot(pivot) + .show(ui.ctx(), |ui| { + // Note: we use a separate clip-rect for this area, so the popup can be outside the parent. + // See https://github.com/emilk/egui/issues/825 + let frame = Frame::popup(ui.style()); + let frame_margin = frame.total_margin(); + frame + .show(ui, |ui| { + ui.with_layout(Layout::top_down_justified(Align::LEFT), |ui| { + ui.set_width(widget_response.rect.width() - frame_margin.sum().x); + add_contents(ui) + }) + .inner + }) + .inner + }) + .inner; + + if ui.input(|i| i.key_pressed(Key::Escape)) || widget_response.clicked_elsewhere() { + ui.memory_mut(|mem| mem.close_popup()); + } + Some(inner) + } else { + None + } +} diff --git a/nevmes-gui/crates/egui/src/containers/resize.rs b/nevmes-gui/crates/egui/src/containers/resize.rs new file mode 100644 index 0000000..befb51a --- /dev/null +++ b/nevmes-gui/crates/egui/src/containers/resize.rs @@ -0,0 +1,351 @@ +use crate::*; + +#[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub(crate) struct State { + /// This is the size that the user has picked by dragging the resize handles. + /// This may be smaller and/or larger than the actual size. + /// For instance, the user may have tried to shrink too much (not fitting the contents). + /// Or the user requested a large area, but the content don't need that much space. + pub(crate) desired_size: Vec2, + + /// Actual size of content last frame + last_content_size: Vec2, + + /// Externally requested size (e.g. by Window) for the next frame + pub(crate) requested_size: Option, +} + +impl State { + pub fn load(ctx: &Context, id: Id) -> Option { + ctx.data_mut(|d| d.get_persisted(id)) + } + + pub fn store(self, ctx: &Context, id: Id) { + ctx.data_mut(|d| d.insert_persisted(id, self)); + } +} + +/// A region that can be resized by dragging the bottom right corner. +#[derive(Clone, Copy, Debug)] +#[must_use = "You should call .show()"] +pub struct Resize { + id: Option, + id_source: Option, + + /// If false, we are no enabled + resizable: bool, + + pub(crate) min_size: Vec2, + pub(crate) max_size: Vec2, + + default_size: Vec2, + + with_stroke: bool, +} + +impl Default for Resize { + fn default() -> Self { + Self { + id: None, + id_source: None, + resizable: true, + min_size: Vec2::splat(16.0), + max_size: Vec2::splat(f32::INFINITY), + default_size: vec2(320.0, 128.0), // TODO(emilk): preferred size of [`Resize`] area. + with_stroke: true, + } + } +} + +impl Resize { + /// Assign an explicit and globally unique id. + pub fn id(mut self, id: Id) -> Self { + self.id = Some(id); + self + } + + /// A source for the unique [`Id`], e.g. `.id_source("second_resize_area")` or `.id_source(loop_index)`. + pub fn id_source(mut self, id_source: impl std::hash::Hash) -> Self { + self.id_source = Some(Id::new(id_source)); + self + } + + /// Preferred / suggested width. Actual width will depend on contents. + /// + /// Examples: + /// * if the contents is text, this will decide where we break long lines. + /// * if the contents is a canvas, this decides the width of it, + /// * if the contents is some buttons, this is ignored and we will auto-size. + pub fn default_width(mut self, width: f32) -> Self { + self.default_size.x = width; + self + } + + /// Preferred / suggested height. Actual height will depend on contents. + /// + /// Examples: + /// * if the contents is a [`ScrollArea`] then this decides the maximum size. + /// * if the contents is a canvas, this decides the height of it, + /// * if the contents is text and buttons, then the `default_height` is ignored + /// and the height is picked automatically.. + pub fn default_height(mut self, height: f32) -> Self { + self.default_size.y = height; + self + } + + pub fn default_size(mut self, default_size: impl Into) -> Self { + self.default_size = default_size.into(); + self + } + + /// Won't shrink to smaller than this + pub fn min_size(mut self, min_size: impl Into) -> Self { + self.min_size = min_size.into(); + self + } + + /// Won't shrink to smaller than this + pub fn min_width(mut self, min_width: f32) -> Self { + self.min_size.x = min_width; + self + } + + /// Won't shrink to smaller than this + pub fn min_height(mut self, min_height: f32) -> Self { + self.min_size.y = min_height; + self + } + + /// Won't expand to larger than this + pub fn max_size(mut self, max_size: impl Into) -> Self { + self.max_size = max_size.into(); + self + } + + /// Can you resize it with the mouse? + /// Note that a window can still auto-resize + pub fn resizable(mut self, resizable: bool) -> Self { + self.resizable = resizable; + self + } + + pub fn is_resizable(&self) -> bool { + self.resizable + } + + /// Not manually resizable, just takes the size of its contents. + /// Text will not wrap, but will instead make your window width expand. + pub fn auto_sized(self) -> Self { + self.min_size(Vec2::ZERO) + .default_size(Vec2::splat(f32::INFINITY)) + .resizable(false) + } + + pub fn fixed_size(mut self, size: impl Into) -> Self { + let size = size.into(); + self.default_size = size; + self.min_size = size; + self.max_size = size; + self.resizable = false; + self + } + + pub fn with_stroke(mut self, with_stroke: bool) -> Self { + self.with_stroke = with_stroke; + self + } +} + +struct Prepared { + id: Id, + state: State, + corner_response: Option, + content_ui: Ui, +} + +impl Resize { + fn begin(&mut self, ui: &mut Ui) -> Prepared { + let position = ui.available_rect_before_wrap().min; + let id = self.id.unwrap_or_else(|| { + let id_source = self.id_source.unwrap_or_else(|| Id::new("resize")); + ui.make_persistent_id(id_source) + }); + + let mut state = State::load(ui.ctx(), id).unwrap_or_else(|| { + ui.ctx().request_repaint(); // counter frame delay + + let default_size = self + .default_size + .at_least(self.min_size) + .at_most(self.max_size) + .at_most( + ui.ctx().screen_rect().size() - ui.spacing().window_margin.sum(), // hack for windows + ); + + State { + desired_size: default_size, + last_content_size: vec2(0.0, 0.0), + requested_size: None, + } + }); + + state.desired_size = state + .desired_size + .at_least(self.min_size) + .at_most(self.max_size); + + let mut user_requested_size = state.requested_size.take(); + + let corner_response = if self.resizable { + // Resize-corner: + let corner_size = Vec2::splat(ui.visuals().resize_corner_size); + let corner_rect = + Rect::from_min_size(position + state.desired_size - corner_size, corner_size); + let corner_response = ui.interact(corner_rect, id.with("corner"), Sense::drag()); + + if let Some(pointer_pos) = corner_response.interact_pointer_pos() { + user_requested_size = + Some(pointer_pos - position + 0.5 * corner_response.rect.size()); + } + + Some(corner_response) + } else { + None + }; + + if let Some(user_requested_size) = user_requested_size { + state.desired_size = user_requested_size; + } else { + // We are not being actively resized, so auto-expand to include size of last frame. + // This prevents auto-shrinking if the contents contain width-filling widgets (separators etc) + // but it makes a lot of interactions with [`Window`]s nicer. + state.desired_size = state.desired_size.max(state.last_content_size); + } + + state.desired_size = state + .desired_size + .at_least(self.min_size) + .at_most(self.max_size); + + // ------------------------------ + + let inner_rect = Rect::from_min_size(position, state.desired_size); + + let mut content_clip_rect = inner_rect.expand(ui.visuals().clip_rect_margin); + + // If we pull the resize handle to shrink, we want to TRY to shrink it. + // After laying out the contents, we might be much bigger. + // In those cases we don't want the clip_rect to be smaller, because + // then we will clip the contents of the region even thought the result gets larger. This is simply ugly! + // So we use the memory of last_content_size to make the clip rect large enough. + content_clip_rect.max = content_clip_rect.max.max( + inner_rect.min + state.last_content_size + Vec2::splat(ui.visuals().clip_rect_margin), + ); + + content_clip_rect = content_clip_rect.intersect(ui.clip_rect()); // Respect parent region + + let mut content_ui = ui.child_ui(inner_rect, *ui.layout()); + content_ui.set_clip_rect(content_clip_rect); + + Prepared { + id, + state, + corner_response, + content_ui, + } + } + + pub fn show(mut self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> R { + let mut prepared = self.begin(ui); + let ret = add_contents(&mut prepared.content_ui); + self.end(ui, prepared); + ret + } + + fn end(self, ui: &mut Ui, prepared: Prepared) { + let Prepared { + id, + mut state, + corner_response, + content_ui, + } = prepared; + + state.last_content_size = content_ui.min_size(); + + // ------------------------------ + + let size = if self.with_stroke || self.resizable { + // We show how large we are, + // so we must follow the contents: + + state.desired_size = state.desired_size.max(state.last_content_size); + + // We are as large as we look + state.desired_size + } else { + // Probably a window. + state.last_content_size + }; + ui.advance_cursor_after_rect(Rect::from_min_size(content_ui.min_rect().min, size)); + + // ------------------------------ + + if self.with_stroke && corner_response.is_some() { + let rect = Rect::from_min_size(content_ui.min_rect().left_top(), state.desired_size); + let rect = rect.expand(2.0); // breathing room for content + ui.painter().add(Shape::rect_stroke( + rect, + 3.0, + ui.visuals().widgets.noninteractive.bg_stroke, + )); + } + + if let Some(corner_response) = corner_response { + paint_resize_corner(ui, &corner_response); + + if corner_response.hovered() || corner_response.dragged() { + ui.ctx().set_cursor_icon(CursorIcon::ResizeNwSe); + } + } + + state.store(ui.ctx(), id); + + if ui.ctx().style().debug.show_resize { + ui.ctx().debug_painter().debug_rect( + Rect::from_min_size(content_ui.min_rect().left_top(), state.desired_size), + Color32::GREEN, + "desired_size", + ); + ui.ctx().debug_painter().debug_rect( + Rect::from_min_size(content_ui.min_rect().left_top(), state.last_content_size), + Color32::LIGHT_BLUE, + "last_content_size", + ); + } + } +} + +use epaint::Stroke; + +pub fn paint_resize_corner(ui: &mut Ui, response: &Response) { + let stroke = ui.style().interact(response).fg_stroke; + paint_resize_corner_with_style(ui, &response.rect, stroke, Align2::RIGHT_BOTTOM); +} + +pub fn paint_resize_corner_with_style(ui: &mut Ui, rect: &Rect, stroke: Stroke, corner: Align2) { + let painter = ui.painter(); + let cp = painter.round_pos_to_pixels(corner.pos_in_rect(rect)); + let mut w = 2.0; + + while w <= rect.width() && w <= rect.height() { + painter.line_segment( + [ + pos2(cp.x - w * corner.x().to_sign(), cp.y), + pos2(cp.x, cp.y - w * corner.y().to_sign()), + ], + stroke, + ); + w += 4.0; + } +} diff --git a/nevmes-gui/crates/egui/src/containers/scroll_area.rs b/nevmes-gui/crates/egui/src/containers/scroll_area.rs new file mode 100644 index 0000000..2df4465 --- /dev/null +++ b/nevmes-gui/crates/egui/src/containers/scroll_area.rs @@ -0,0 +1,924 @@ +//! Coordinate system names: +//! * content: size of contents (generally large; that's why we want scroll bars) +//! * outer: size of scroll area including scroll bar(s) +//! * inner: excluding scroll bar(s). The area we clip the contents to. + +#![allow(clippy::needless_range_loop)] + +use crate::*; + +#[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr(feature = "serde", serde(default))] +pub struct State { + /// Positive offset means scrolling down/right + pub offset: Vec2, + + /// Were the scroll bars visible last frame? + show_scroll: [bool; 2], + + /// The content were to large to fit large frame. + content_is_too_large: [bool; 2], + + /// Momentum, used for kinetic scrolling + #[cfg_attr(feature = "serde", serde(skip))] + vel: Vec2, + + /// Mouse offset relative to the top of the handle when started moving the handle. + scroll_start_offset_from_top_left: [Option; 2], + + /// Is the scroll sticky. This is true while scroll handle is in the end position + /// and remains that way until the user moves the scroll_handle. Once unstuck (false) + /// it remains false until the scroll touches the end position, which reenables stickiness. + scroll_stuck_to_end: [bool; 2], +} + +impl Default for State { + fn default() -> Self { + Self { + offset: Vec2::ZERO, + show_scroll: [false; 2], + content_is_too_large: [false; 2], + vel: Vec2::ZERO, + scroll_start_offset_from_top_left: [None; 2], + scroll_stuck_to_end: [true; 2], + } + } +} + +impl State { + pub fn load(ctx: &Context, id: Id) -> Option { + ctx.data_mut(|d| d.get_persisted(id)) + } + + pub fn store(self, ctx: &Context, id: Id) { + ctx.data_mut(|d| d.insert_persisted(id, self)); + } +} + +pub struct ScrollAreaOutput { + /// What the user closure returned. + pub inner: R, + + /// [`Id`] of the [`ScrollArea`]. + pub id: Id, + + /// The current state of the scroll area. + pub state: State, + + /// The size of the content. If this is larger than [`Self::inner_rect`], + /// then there was need for scrolling. + pub content_size: Vec2, + + /// Where on the screen the content is (excludes scroll bars). + pub inner_rect: Rect, +} + +/// Indicate whether the horizontal and vertical scroll bars must be always visible, hidden or visible when needed. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum ScrollBarVisibility { + AlwaysVisible, + VisibleWhenNeeded, + AlwaysHidden, +} + +/// Add vertical and/or horizontal scrolling to a contained [`Ui`]. +/// +/// ``` +/// # egui::__run_test_ui(|ui| { +/// egui::ScrollArea::vertical().show(ui, |ui| { +/// // Add a lot of widgets here. +/// }); +/// # }); +/// ``` +/// +/// You can scroll to an element using [`Response::scroll_to_me`], [`Ui::scroll_to_cursor`] and [`Ui::scroll_to_rect`]. +#[derive(Clone, Debug)] +#[must_use = "You should call .show()"] +pub struct ScrollArea { + /// Do we have horizontal/vertical scrolling? + has_bar: [bool; 2], + auto_shrink: [bool; 2], + max_size: Vec2, + min_scrolled_size: Vec2, + scroll_bar_visibility: ScrollBarVisibility, + id_source: Option, + offset_x: Option, + offset_y: Option, + + /// If false, we ignore scroll events. + scrolling_enabled: bool, + drag_to_scroll: bool, + + /// If true for vertical or horizontal the scroll wheel will stick to the + /// end position until user manually changes position. It will become true + /// again once scroll handle makes contact with end. + stick_to_end: [bool; 2], +} + +impl ScrollArea { + /// Create a horizontal scroll area. + pub fn horizontal() -> Self { + Self::new([true, false]) + } + + /// Create a vertical scroll area. + pub fn vertical() -> Self { + Self::new([false, true]) + } + + /// Create a bi-directional (horizontal and vertical) scroll area. + pub fn both() -> Self { + Self::new([true, true]) + } + + /// Create a scroll area where both direction of scrolling is disabled. + /// It's unclear why you would want to do this. + pub fn neither() -> Self { + Self::new([false, false]) + } + + /// Create a scroll area where you decide which axis has scrolling enabled. + /// For instance, `ScrollArea::new([true, false])` enables horizontal scrolling. + pub fn new(has_bar: [bool; 2]) -> Self { + Self { + has_bar, + auto_shrink: [true; 2], + max_size: Vec2::INFINITY, + min_scrolled_size: Vec2::splat(64.0), + scroll_bar_visibility: ScrollBarVisibility::AlwaysHidden, + id_source: None, + offset_x: None, + offset_y: None, + scrolling_enabled: true, + drag_to_scroll: true, + stick_to_end: [false; 2], + } + } + + /// The maximum width of the outer frame of the scroll area. + /// + /// Use `f32::INFINITY` if you want the scroll area to expand to fit the surrounding [`Ui`] (default). + /// + /// See also [`Self::auto_shrink`]. + pub fn max_width(mut self, max_width: f32) -> Self { + self.max_size.x = max_width; + self + } + + /// The maximum height of the outer frame of the scroll area. + /// + /// Use `f32::INFINITY` if you want the scroll area to expand to fit the surrounding [`Ui`] (default). + /// + /// See also [`Self::auto_shrink`]. + pub fn max_height(mut self, max_height: f32) -> Self { + self.max_size.y = max_height; + self + } + + /// The minimum width of a horizontal scroll area which requires scroll bars. + /// + /// The [`ScrollArea`] will only become smaller than this if the content is smaller than this + /// (and so we don't require scroll bars). + /// + /// Default: `64.0`. + pub fn min_scrolled_width(mut self, min_scrolled_width: f32) -> Self { + self.min_scrolled_size.x = min_scrolled_width; + self + } + + /// The minimum height of a vertical scroll area which requires scroll bars. + /// + /// The [`ScrollArea`] will only become smaller than this if the content is smaller than this + /// (and so we don't require scroll bars). + /// + /// Default: `64.0`. + pub fn min_scrolled_height(mut self, min_scrolled_height: f32) -> Self { + self.min_scrolled_size.y = min_scrolled_height; + self + } + + /// Set the visibility of both horizontal and vertical scroll bars. + /// + /// With `ScrollBarVisibility::VisibleWhenNeeded` (default), the scroll bar will be visible only when needed. + pub fn scroll_bar_visibility(mut self, scroll_bar_visibility: ScrollBarVisibility) -> Self { + self.scroll_bar_visibility = scroll_bar_visibility; + self + } + + /// A source for the unique [`Id`], e.g. `.id_source("second_scroll_area")` or `.id_source(loop_index)`. + pub fn id_source(mut self, id_source: impl std::hash::Hash) -> Self { + self.id_source = Some(Id::new(id_source)); + self + } + + /// Set the horizontal and vertical scroll offset position. + /// + /// Positive offset means scrolling down/right. + /// + /// See also: [`Self::vertical_scroll_offset`], [`Self::horizontal_scroll_offset`], + /// [`Ui::scroll_to_cursor`](crate::ui::Ui::scroll_to_cursor) and + /// [`Response::scroll_to_me`](crate::Response::scroll_to_me) + pub fn scroll_offset(mut self, offset: Vec2) -> Self { + self.offset_x = Some(offset.x); + self.offset_y = Some(offset.y); + self + } + + /// Set the vertical scroll offset position. + /// + /// Positive offset means scrolling down. + /// + /// See also: [`Self::scroll_offset`], [`Ui::scroll_to_cursor`](crate::ui::Ui::scroll_to_cursor) and + /// [`Response::scroll_to_me`](crate::Response::scroll_to_me) + pub fn vertical_scroll_offset(mut self, offset: f32) -> Self { + self.offset_y = Some(offset); + self + } + + /// Set the horizontal scroll offset position. + /// + /// Positive offset means scrolling right. + /// + /// See also: [`Self::scroll_offset`], [`Ui::scroll_to_cursor`](crate::ui::Ui::scroll_to_cursor) and + /// [`Response::scroll_to_me`](crate::Response::scroll_to_me) + pub fn horizontal_scroll_offset(mut self, offset: f32) -> Self { + self.offset_x = Some(offset); + self + } + + /// Turn on/off scrolling on the horizontal axis. + pub fn hscroll(mut self, hscroll: bool) -> Self { + self.has_bar[0] = hscroll; + self + } + + /// Turn on/off scrolling on the vertical axis. + pub fn vscroll(mut self, vscroll: bool) -> Self { + self.has_bar[1] = vscroll; + self + } + + /// Turn on/off scrolling on the horizontal/vertical axes. + pub fn scroll2(mut self, has_bar: [bool; 2]) -> Self { + self.has_bar = has_bar; + self + } + + /// Control the scrolling behavior. + /// + /// * If `true` (default), the scroll area will respond to user scrolling. + /// * If `false`, the scroll area will not respond to user scrolling. + /// + /// This can be used, for example, to optionally freeze scrolling while the user + /// is typing text in a [`TextEdit`] widget contained within the scroll area. + /// + /// This controls both scrolling directions. + pub fn enable_scrolling(mut self, enable: bool) -> Self { + self.scrolling_enabled = enable; + self + } + + /// Can the user drag the scroll area to scroll? + /// + /// This is useful for touch screens. + /// + /// If `true`, the [`ScrollArea`] will sense drags. + /// + /// Default: `true`. + pub fn drag_to_scroll(mut self, drag_to_scroll: bool) -> Self { + self.drag_to_scroll = drag_to_scroll; + self + } + + /// For each axis, should the containing area shrink if the content is small? + /// + /// * If `true`, egui will add blank space outside the scroll area. + /// * If `false`, egui will add blank space inside the scroll area. + /// + /// Default: `[true; 2]`. + pub fn auto_shrink(mut self, auto_shrink: [bool; 2]) -> Self { + self.auto_shrink = auto_shrink; + self + } + + pub(crate) fn has_any_bar(&self) -> bool { + self.has_bar[0] || self.has_bar[1] + } + + /// The scroll handle will stick to the rightmost position even while the content size + /// changes dynamically. This can be useful to simulate text scrollers coming in from right + /// hand side. The scroll handle remains stuck until user manually changes position. Once "unstuck" + /// it will remain focused on whatever content viewport the user left it on. If the scroll + /// handle is dragged all the way to the right it will again become stuck and remain there + /// until manually pulled from the end position. + pub fn stick_to_right(mut self, stick: bool) -> Self { + self.stick_to_end[0] = stick; + self + } + + /// The scroll handle will stick to the bottom position even while the content size + /// changes dynamically. This can be useful to simulate terminal UIs or log/info scrollers. + /// The scroll handle remains stuck until user manually changes position. Once "unstuck" + /// it will remain focused on whatever content viewport the user left it on. If the scroll + /// handle is dragged to the bottom it will again become stuck and remain there until manually + /// pulled from the end position. + pub fn stick_to_bottom(mut self, stick: bool) -> Self { + self.stick_to_end[1] = stick; + self + } +} + +struct Prepared { + id: Id, + state: State, + has_bar: [bool; 2], + auto_shrink: [bool; 2], + /// How much horizontal and vertical space are used up by the + /// width of the vertical bar, and the height of the horizontal bar? + current_bar_use: Vec2, + scroll_bar_visibility: ScrollBarVisibility, + /// Where on the screen the content is (excludes scroll bars). + inner_rect: Rect, + content_ui: Ui, + /// Relative coordinates: the offset and size of the view of the inner UI. + /// `viewport.min == ZERO` means we scrolled to the top. + viewport: Rect, + scrolling_enabled: bool, + stick_to_end: [bool; 2], +} + +impl ScrollArea { + fn begin(self, ui: &mut Ui) -> Prepared { + let Self { + has_bar, + auto_shrink, + max_size, + min_scrolled_size, + scroll_bar_visibility, + id_source, + offset_x, + offset_y, + scrolling_enabled, + drag_to_scroll, + stick_to_end, + } = self; + + let ctx = ui.ctx().clone(); + + let id_source = id_source.unwrap_or_else(|| Id::new("scroll_area")); + let id = ui.make_persistent_id(id_source); + ui.ctx().check_for_id_clash( + id, + Rect::from_min_size(ui.available_rect_before_wrap().min, Vec2::ZERO), + "ScrollArea", + ); + let mut state = State::load(&ctx, id).unwrap_or_default(); + + state.offset.x = offset_x.unwrap_or(state.offset.x); + state.offset.y = offset_y.unwrap_or(state.offset.y); + + let max_scroll_bar_width = max_scroll_bar_width_with_margin(ui); + + let current_hscroll_bar_height = if !has_bar[0] { + 0.0 + } else if scroll_bar_visibility == ScrollBarVisibility::AlwaysVisible { + max_scroll_bar_width + } else { + max_scroll_bar_width * ui.ctx().animate_bool(id.with("h"), state.show_scroll[0]) + }; + + let current_vscroll_bar_width = if !has_bar[1] { + 0.0 + } else if scroll_bar_visibility == ScrollBarVisibility::AlwaysVisible { + max_scroll_bar_width + } else { + max_scroll_bar_width * ui.ctx().animate_bool(id.with("v"), state.show_scroll[1]) + }; + + let current_bar_use = vec2(current_vscroll_bar_width, current_hscroll_bar_height); + + let available_outer = ui.available_rect_before_wrap(); + + let outer_size = available_outer.size().at_most(max_size); + + let inner_size = { + let mut inner_size = outer_size - current_bar_use; + + // Don't go so far that we shrink to zero. + // In particular, if we put a [`ScrollArea`] inside of a [`ScrollArea`], the inner + // one shouldn't collapse into nothingness. + // See https://github.com/emilk/egui/issues/1097 + for d in 0..2 { + if has_bar[d] { + inner_size[d] = inner_size[d].max(min_scrolled_size[d]); + } + } + inner_size + }; + + let inner_rect = Rect::from_min_size(available_outer.min, inner_size); + + let mut content_max_size = inner_size; + + if true { + // Tell the inner Ui to *try* to fit the content without needing to scroll, + // i.e. better to wrap text and shrink images than showing a horizontal scrollbar! + } else { + // Tell the inner Ui to use as much space as possible, we can scroll to see it! + for d in 0..2 { + if has_bar[d] { + content_max_size[d] = f32::INFINITY; + } + } + } + + let content_max_rect = Rect::from_min_size(inner_rect.min - state.offset, content_max_size); + let mut content_ui = ui.child_ui(content_max_rect, *ui.layout()); + + { + // Clip the content, but only when we really need to: + let clip_rect_margin = ui.visuals().clip_rect_margin; + let scroll_bar_inner_margin = ui.spacing().scroll_bar_inner_margin; + let mut content_clip_rect = ui.clip_rect(); + for d in 0..2 { + if has_bar[d] { + if state.content_is_too_large[d] { + content_clip_rect.min[d] = inner_rect.min[d] - clip_rect_margin; + content_clip_rect.max[d] = inner_rect.max[d] + clip_rect_margin; + } + + if state.show_scroll[d] { + // Make sure content doesn't cover scroll bars + let tiny_gap = 1.0; + content_clip_rect.max[1 - d] = + inner_rect.max[1 - d] + scroll_bar_inner_margin - tiny_gap; + } + } else { + // Nice handling of forced resizing beyond the possible: + content_clip_rect.max[d] = ui.clip_rect().max[d] - current_bar_use[d]; + } + } + // Make sure we din't accidentally expand the clip rect + content_clip_rect = content_clip_rect.intersect(ui.clip_rect()); + content_ui.set_clip_rect(content_clip_rect); + } + + let viewport = Rect::from_min_size(Pos2::ZERO + state.offset, inner_size); + + if (scrolling_enabled && drag_to_scroll) + && (state.content_is_too_large[0] || state.content_is_too_large[1]) + { + // Drag contents to scroll (for touch screens mostly). + // We must do this BEFORE adding content to the `ScrollArea`, + // or we will steal input from the widgets we contain. + let content_response = ui.interact(inner_rect, id.with("area"), Sense::drag()); + + if content_response.dragged() { + for d in 0..2 { + if has_bar[d] { + ui.input(|input| { + state.offset[d] -= input.pointer.delta()[d]; + state.vel[d] = input.pointer.velocity()[d]; + }); + state.scroll_stuck_to_end[d] = false; + } else { + state.vel[d] = 0.0; + } + } + } else { + let stop_speed = 20.0; // Pixels per second. + let friction_coeff = 1000.0; // Pixels per second squared. + let dt = ui.input(|i| i.unstable_dt); + + let friction = friction_coeff * dt; + if friction > state.vel.length() || state.vel.length() < stop_speed { + state.vel = Vec2::ZERO; + } else { + state.vel -= friction * state.vel.normalized(); + // Offset has an inverted coordinate system compared to + // the velocity, so we subtract it instead of adding it + state.offset -= state.vel * dt; + ui.ctx().request_repaint(); + } + } + } + + Prepared { + id, + state, + has_bar, + auto_shrink, + current_bar_use, + scroll_bar_visibility, + inner_rect, + content_ui, + viewport, + scrolling_enabled, + stick_to_end, + } + } + + /// Show the [`ScrollArea`], and add the contents to the viewport. + /// + /// If the inner area can be very long, consider using [`Self::show_rows`] instead. + pub fn show( + self, + ui: &mut Ui, + add_contents: impl FnOnce(&mut Ui) -> R, + ) -> ScrollAreaOutput { + self.show_viewport_dyn(ui, Box::new(|ui, _viewport| add_contents(ui))) + } + + /// Efficiently show only the visible part of a large number of rows. + /// + /// ``` + /// # egui::__run_test_ui(|ui| { + /// let text_style = egui::TextStyle::Body; + /// let row_height = ui.text_style_height(&text_style); + /// // let row_height = ui.spacing().interact_size.y; // if you are adding buttons instead of labels. + /// let total_rows = 10_000; + /// egui::ScrollArea::vertical().show_rows(ui, row_height, total_rows, |ui, row_range| { + /// for row in row_range { + /// let text = format!("Row {}/{}", row + 1, total_rows); + /// ui.label(text); + /// } + /// }); + /// # }); + /// ``` + pub fn show_rows( + self, + ui: &mut Ui, + row_height_sans_spacing: f32, + total_rows: usize, + add_contents: impl FnOnce(&mut Ui, std::ops::Range) -> R, + ) -> ScrollAreaOutput { + let spacing = ui.spacing().item_spacing; + let row_height_with_spacing = row_height_sans_spacing + spacing.y; + self.show_viewport(ui, |ui, viewport| { + ui.set_height((row_height_with_spacing * total_rows as f32 - spacing.y).at_least(0.0)); + + let mut min_row = (viewport.min.y / row_height_with_spacing).floor() as usize; + let mut max_row = (viewport.max.y / row_height_with_spacing).ceil() as usize + 1; + if max_row > total_rows { + let diff = max_row.saturating_sub(min_row); + max_row = total_rows; + min_row = total_rows.saturating_sub(diff); + } + + let y_min = ui.max_rect().top() + min_row as f32 * row_height_with_spacing; + let y_max = ui.max_rect().top() + max_row as f32 * row_height_with_spacing; + + let rect = Rect::from_x_y_ranges(ui.max_rect().x_range(), y_min..=y_max); + + ui.allocate_ui_at_rect(rect, |viewport_ui| { + viewport_ui.skip_ahead_auto_ids(min_row); // Make sure we get consistent IDs. + add_contents(viewport_ui, min_row..max_row) + }) + .inner + }) + } + + /// This can be used to only paint the visible part of the contents. + /// + /// `add_contents` is given the viewport rectangle, which is the relative view of the content. + /// So if the passed rect has min = zero, then show the top left content (the user has not scrolled). + pub fn show_viewport( + self, + ui: &mut Ui, + add_contents: impl FnOnce(&mut Ui, Rect) -> R, + ) -> ScrollAreaOutput { + self.show_viewport_dyn(ui, Box::new(add_contents)) + } + + fn show_viewport_dyn<'c, R>( + self, + ui: &mut Ui, + add_contents: Box R + 'c>, + ) -> ScrollAreaOutput { + let mut prepared = self.begin(ui); + let id = prepared.id; + let inner_rect = prepared.inner_rect; + let inner = add_contents(&mut prepared.content_ui, prepared.viewport); + let (content_size, state) = prepared.end(ui); + ScrollAreaOutput { + inner, + id, + state, + content_size, + inner_rect, + } + } +} + +impl Prepared { + /// Returns content size and state + fn end(self, ui: &mut Ui) -> (Vec2, State) { + let Prepared { + id, + mut state, + inner_rect, + has_bar, + auto_shrink, + mut current_bar_use, + scroll_bar_visibility, + content_ui, + viewport: _, + scrolling_enabled, + stick_to_end, + } = self; + + let content_size = content_ui.min_size(); + + for d in 0..2 { + if has_bar[d] { + // We take the scroll target so only this ScrollArea will use it: + let scroll_target = content_ui + .ctx() + .frame_state_mut(|state| state.scroll_target[d].take()); + if let Some((scroll, align)) = scroll_target { + let min = content_ui.min_rect().min[d]; + let clip_rect = content_ui.clip_rect(); + let visible_range = min..=min + clip_rect.size()[d]; + let start = *scroll.start(); + let end = *scroll.end(); + let clip_start = clip_rect.min[d]; + let clip_end = clip_rect.max[d]; + let mut spacing = ui.spacing().item_spacing[d]; + + let delta = if let Some(align) = align { + let center_factor = align.to_factor(); + + let offset = + lerp(scroll, center_factor) - lerp(visible_range, center_factor); + + // Depending on the alignment we need to add or subtract the spacing + spacing *= remap(center_factor, 0.0..=1.0, -1.0..=1.0); + + offset + spacing - state.offset[d] + } else if start < clip_start && end < clip_end { + -(clip_start - start + spacing).min(clip_end - end - spacing) + } else if end > clip_end && start > clip_start { + (end - clip_end + spacing).min(start - clip_start - spacing) + } else { + // Ui is already in view, no need to adjust scroll. + 0.0 + }; + + if delta != 0.0 { + state.offset[d] += delta; + ui.ctx().request_repaint(); + } + } + } + } + + let inner_rect = { + // At this point this is the available size for the inner rect. + let mut inner_size = inner_rect.size(); + + for d in 0..2 { + inner_size[d] = match (has_bar[d], auto_shrink[d]) { + (true, true) => inner_size[d].min(content_size[d]), // shrink scroll area if content is small + (true, false) => inner_size[d], // let scroll area be larger than content; fill with blank space + (false, true) => content_size[d], // Follow the content (expand/contract to fit it). + (false, false) => inner_size[d].max(content_size[d]), // Expand to fit content + }; + } + + Rect::from_min_size(inner_rect.min, inner_size) + }; + + let outer_rect = Rect::from_min_size(inner_rect.min, inner_rect.size() + current_bar_use); + + let content_is_too_large = [ + content_size.x > inner_rect.width(), + content_size.y > inner_rect.height(), + ]; + + let max_offset = content_size - inner_rect.size(); + if scrolling_enabled && ui.rect_contains_pointer(outer_rect) { + for d in 0..2 { + if has_bar[d] { + let scroll_delta = ui.ctx().frame_state(|fs| fs.scroll_delta); + + let scrolling_up = state.offset[d] > 0.0 && scroll_delta[d] > 0.0; + let scrolling_down = state.offset[d] < max_offset[d] && scroll_delta[d] < 0.0; + + if scrolling_up || scrolling_down { + state.offset[d] -= scroll_delta[d]; + // Clear scroll delta so no parent scroll will use it. + ui.ctx().frame_state_mut(|fs| fs.scroll_delta[d] = 0.0); + state.scroll_stuck_to_end[d] = false; + } + } + } + } + + let show_scroll_this_frame = match scroll_bar_visibility { + ScrollBarVisibility::AlwaysVisible => [true, true], + ScrollBarVisibility::VisibleWhenNeeded => { + [content_is_too_large[0], content_is_too_large[1]] + } + ScrollBarVisibility::AlwaysHidden => [false, false], + }; + + let max_scroll_bar_width = max_scroll_bar_width_with_margin(ui); + + // Avoid frame delay; start showing scroll bar right away: + if show_scroll_this_frame[0] && current_bar_use.y <= 0.0 { + current_bar_use.y = max_scroll_bar_width * ui.ctx().animate_bool(id.with("h"), true); + } + if show_scroll_this_frame[1] && current_bar_use.x <= 0.0 { + current_bar_use.x = max_scroll_bar_width * ui.ctx().animate_bool(id.with("v"), true); + } + + for d in 0..2 { + let animation_t = current_bar_use[1 - d] / max_scroll_bar_width; + + if animation_t == 0.0 { + continue; + } + + // margin on either side of the scroll bar + let inner_margin = animation_t * ui.spacing().scroll_bar_inner_margin; + let outer_margin = animation_t * ui.spacing().scroll_bar_outer_margin; + let mut min_cross = inner_rect.max[1 - d] + inner_margin; // left of vertical scroll (d == 1) + let mut max_cross = outer_rect.max[1 - d] - outer_margin; // right of vertical scroll (d == 1) + let min_main = inner_rect.min[d]; // top of vertical scroll (d == 1) + let max_main = inner_rect.max[d]; // bottom of vertical scroll (d == 1) + + if ui.clip_rect().max[1 - d] < max_cross + outer_margin { + // Move the scrollbar so it is visible. This is needed in some cases. + // For instance: + // * When we have a vertical-only scroll area in a top level panel, + // and that panel is not wide enough for the contents. + // * When one ScrollArea is nested inside another, and the outer + // is scrolled so that the scroll-bars of the inner ScrollArea (us) + // is outside the clip rectangle. + // Really this should use the tighter clip_rect that ignores clip_rect_margin, but we don't store that. + // clip_rect_margin is quite a hack. It would be nice to get rid of it. + let width = max_cross - min_cross; + max_cross = ui.clip_rect().max[1 - d] - outer_margin; + min_cross = max_cross - width; + } + + let outer_scroll_rect = if d == 0 { + Rect::from_min_max( + pos2(inner_rect.left(), min_cross), + pos2(inner_rect.right(), max_cross), + ) + } else { + Rect::from_min_max( + pos2(min_cross, inner_rect.top()), + pos2(max_cross, inner_rect.bottom()), + ) + }; + + // maybe force increase in offset to keep scroll stuck to end position + if stick_to_end[d] && state.scroll_stuck_to_end[d] { + state.offset[d] = content_size[d] - inner_rect.size()[d]; + } + + let from_content = + |content| remap_clamp(content, 0.0..=content_size[d], min_main..=max_main); + + let handle_rect = if d == 0 { + Rect::from_min_max( + pos2(from_content(state.offset.x), min_cross), + pos2(from_content(state.offset.x + inner_rect.width()), max_cross), + ) + } else { + Rect::from_min_max( + pos2(min_cross, from_content(state.offset.y)), + pos2( + max_cross, + from_content(state.offset.y + inner_rect.height()), + ), + ) + }; + + let interact_id = id.with(d); + let sense = if self.scrolling_enabled { + Sense::click_and_drag() + } else { + Sense::hover() + }; + let response = ui.interact(outer_scroll_rect, interact_id, sense); + + if let Some(pointer_pos) = response.interact_pointer_pos() { + let scroll_start_offset_from_top_left = state.scroll_start_offset_from_top_left[d] + .get_or_insert_with(|| { + if handle_rect.contains(pointer_pos) { + pointer_pos[d] - handle_rect.min[d] + } else { + let handle_top_pos_at_bottom = max_main - handle_rect.size()[d]; + // Calculate the new handle top position, centering the handle on the mouse. + let new_handle_top_pos = (pointer_pos[d] - handle_rect.size()[d] / 2.0) + .clamp(min_main, handle_top_pos_at_bottom); + pointer_pos[d] - new_handle_top_pos + } + }); + + let new_handle_top = pointer_pos[d] - *scroll_start_offset_from_top_left; + state.offset[d] = remap(new_handle_top, min_main..=max_main, 0.0..=content_size[d]); + + // some manual action taken, scroll not stuck + state.scroll_stuck_to_end[d] = false; + } else { + state.scroll_start_offset_from_top_left[d] = None; + } + + let unbounded_offset = state.offset[d]; + state.offset[d] = state.offset[d].max(0.0); + state.offset[d] = state.offset[d].min(max_offset[d]); + + if state.offset[d] != unbounded_offset { + state.vel[d] = 0.0; + } + + if ui.is_rect_visible(outer_scroll_rect) { + // Avoid frame-delay by calculating a new handle rect: + let mut handle_rect = if d == 0 { + Rect::from_min_max( + pos2(from_content(state.offset.x), min_cross), + pos2(from_content(state.offset.x + inner_rect.width()), max_cross), + ) + } else { + Rect::from_min_max( + pos2(min_cross, from_content(state.offset.y)), + pos2( + max_cross, + from_content(state.offset.y + inner_rect.height()), + ), + ) + }; + let min_handle_size = ui.spacing().scroll_handle_min_length; + if handle_rect.size()[d] < min_handle_size { + handle_rect = Rect::from_center_size( + handle_rect.center(), + if d == 0 { + vec2(min_handle_size, handle_rect.size().y) + } else { + vec2(handle_rect.size().x, min_handle_size) + }, + ); + } + + let visuals = if scrolling_enabled { + ui.style().interact(&response) + } else { + &ui.style().visuals.widgets.inactive + }; + + ui.painter().add(epaint::Shape::rect_filled( + outer_scroll_rect, + visuals.rounding, + ui.visuals().extreme_bg_color, + )); + + ui.painter().add(epaint::Shape::rect_filled( + handle_rect, + visuals.rounding, + visuals.bg_fill, + )); + } + } + + ui.advance_cursor_after_rect(outer_rect); + + if show_scroll_this_frame != state.show_scroll { + ui.ctx().request_repaint(); + } + + let available_offset = content_size - inner_rect.size(); + state.offset = state.offset.min(available_offset); + state.offset = state.offset.max(Vec2::ZERO); + + // Is scroll handle at end of content, or is there no scrollbar + // yet (not enough content), but sticking is requested? If so, enter sticky mode. + // Only has an effect if stick_to_end is enabled but we save in + // state anyway so that entering sticky mode at an arbitrary time + // has appropriate effect. + state.scroll_stuck_to_end = [ + (state.offset[0] == available_offset[0]) + || (self.stick_to_end[0] && available_offset[0] < 0.), + (state.offset[1] == available_offset[1]) + || (self.stick_to_end[1] && available_offset[1] < 0.), + ]; + + state.show_scroll = show_scroll_this_frame; + state.content_is_too_large = content_is_too_large; + + state.store(ui.ctx(), id); + + (content_size, state) + } +} + +/// Width of a vertical scrollbar, or height of a horizontal scroll bar +fn max_scroll_bar_width_with_margin(ui: &Ui) -> f32 { + ui.spacing().scroll_bar_inner_margin + + ui.spacing().scroll_bar_width + + ui.spacing().scroll_bar_outer_margin +} diff --git a/nevmes-gui/crates/egui/src/containers/window.rs b/nevmes-gui/crates/egui/src/containers/window.rs new file mode 100644 index 0000000..d5c3f68 --- /dev/null +++ b/nevmes-gui/crates/egui/src/containers/window.rs @@ -0,0 +1,982 @@ +// WARNING: the code in here is horrible. It is a behemoth that needs breaking up into simpler parts. + +use crate::collapsing_header::CollapsingState; +use crate::{widget_text::WidgetTextGalley, *}; +use epaint::*; + +use super::*; + +/// Builder for a floating window which can be dragged, closed, collapsed, resized and scrolled (off by default). +/// +/// You can customize: +/// * title +/// * default, minimum, maximum and/or fixed size, collapsed/expanded +/// * if the window has a scroll area (off by default) +/// * if the window can be collapsed (minimized) to just the title bar (yes, by default) +/// * if there should be a close button (none by default) +/// +/// ``` +/// # egui::__run_test_ctx(|ctx| { +/// egui::Window::new("My Window").show(ctx, |ui| { +/// ui.label("Hello World!"); +/// }); +/// # }); +#[must_use = "You should call .show()"] +pub struct Window<'open> { + title: WidgetText, + open: Option<&'open mut bool>, + area: Area, + frame: Option, + resize: Resize, + scroll: ScrollArea, + collapsible: bool, + default_open: bool, + with_title_bar: bool, +} + +impl<'open> Window<'open> { + /// The window title is used as a unique [`Id`] and must be unique, and should not change. + /// This is true even if you disable the title bar with `.title_bar(false)`. + /// If you need a changing title, you must call `window.id(…)` with a fixed id. + pub fn new(title: impl Into) -> Self { + let title = title.into().fallback_text_style(TextStyle::Heading); + let area = Area::new(Id::new(title.text())); + Self { + title, + open: None, + area, + frame: None, + resize: Resize::default() + .with_stroke(false) + .min_size([96.0, 32.0]) + .default_size([340.0, 420.0]), // Default inner size of a window + scroll: ScrollArea::neither(), + collapsible: true, + default_open: true, + with_title_bar: true, + } + } + + /// Assign a unique id to the Window. Required if the title changes, or is shared with another window. + pub fn id(mut self, id: Id) -> Self { + self.area = self.area.id(id); + self + } + + /// Call this to add a close-button to the window title bar. + /// + /// * If `*open == false`, the window will not be visible. + /// * If `*open == true`, the window will have a close button. + /// * If the close button is pressed, `*open` will be set to `false`. + pub fn open(mut self, open: &'open mut bool) -> Self { + self.open = Some(open); + self + } + + /// If `false` the window will be grayed out and non-interactive. + pub fn enabled(mut self, enabled: bool) -> Self { + self.area = self.area.enabled(enabled); + self + } + + /// If `false` the window will be non-interactive. + pub fn interactable(mut self, interactable: bool) -> Self { + self.area = self.area.interactable(interactable); + self + } + + /// If `false` the window will be immovable. + pub fn movable(mut self, movable: bool) -> Self { + self.area = self.area.movable(movable); + self + } + + /// Usage: `Window::new(…).mutate(|w| w.resize = w.resize.auto_expand_width(true))` + // TODO(emilk): I'm not sure this is a good interface for this. + pub fn mutate(mut self, mutate: impl Fn(&mut Self)) -> Self { + mutate(&mut self); + self + } + + /// Usage: `Window::new(…).resize(|r| r.auto_expand_width(true))` + // TODO(emilk): I'm not sure this is a good interface for this. + pub fn resize(mut self, mutate: impl Fn(Resize) -> Resize) -> Self { + self.resize = mutate(self.resize); + self + } + + /// Change the background color, margins, etc. + pub fn frame(mut self, frame: Frame) -> Self { + self.frame = Some(frame); + self + } + + /// Set minimum width of the window. + pub fn min_width(mut self, min_width: f32) -> Self { + self.resize = self.resize.min_width(min_width); + self + } + + /// Set minimum height of the window. + pub fn min_height(mut self, min_height: f32) -> Self { + self.resize = self.resize.min_height(min_height); + self + } + + /// Set current position of the window. + /// If the window is movable it is up to you to keep track of where it moved to! + pub fn current_pos(mut self, current_pos: impl Into) -> Self { + self.area = self.area.current_pos(current_pos); + self + } + + /// Set initial position of the window. + pub fn default_pos(mut self, default_pos: impl Into) -> Self { + self.area = self.area.default_pos(default_pos); + self + } + + /// Sets the window position and prevents it from being dragged around. + pub fn fixed_pos(mut self, pos: impl Into) -> Self { + self.area = self.area.fixed_pos(pos); + self + } + + /// Constrains this window to the screen bounds. + pub fn constrain(mut self, constrain: bool) -> Self { + self.area = self.area.constrain(constrain); + self + } + + /// Where the "root" of the window is. + /// + /// For instance, if you set this to [`Align2::RIGHT_TOP`] + /// then [`Self::fixed_pos`] will set the position of the right-top + /// corner of the window. + /// + /// Default: [`Align2::LEFT_TOP`]. + pub fn pivot(mut self, pivot: Align2) -> Self { + self.area = self.area.pivot(pivot); + self + } + + /// Set anchor and distance. + /// + /// An anchor of `Align2::RIGHT_TOP` means "put the right-top corner of the window + /// in the right-top corner of the screen". + /// + /// The offset is added to the position, so e.g. an offset of `[-5.0, 5.0]` + /// would move the window left and down from the given anchor. + /// + /// Anchoring also makes the window immovable. + /// + /// It is an error to set both an anchor and a position. + pub fn anchor(mut self, align: Align2, offset: impl Into) -> Self { + self.area = self.area.anchor(align, offset); + self + } + + /// Set initial collapsed state of the window + pub fn default_open(mut self, default_open: bool) -> Self { + self.default_open = default_open; + self + } + + /// Set initial size of the window. + pub fn default_size(mut self, default_size: impl Into) -> Self { + self.resize = self.resize.default_size(default_size); + self + } + + /// Set initial width of the window. + pub fn default_width(mut self, default_width: f32) -> Self { + self.resize = self.resize.default_width(default_width); + self + } + + /// Set initial height of the window. + pub fn default_height(mut self, default_height: f32) -> Self { + self.resize = self.resize.default_height(default_height); + self + } + + /// Sets the window size and prevents it from being resized by dragging its edges. + pub fn fixed_size(mut self, size: impl Into) -> Self { + self.resize = self.resize.fixed_size(size); + self + } + + /// Set initial position and size of the window. + pub fn default_rect(self, rect: Rect) -> Self { + self.default_pos(rect.min).default_size(rect.size()) + } + + /// Sets the window pos and size and prevents it from being moved and resized by dragging its edges. + pub fn fixed_rect(self, rect: Rect) -> Self { + self.fixed_pos(rect.min).fixed_size(rect.size()) + } + + /// Can the user resize the window by dragging its edges? + /// Note that even if you set this to `false` the window may still auto-resize. + pub fn resizable(mut self, resizable: bool) -> Self { + self.resize = self.resize.resizable(resizable); + self + } + + /// Can the window be collapsed by clicking on its title? + pub fn collapsible(mut self, collapsible: bool) -> Self { + self.collapsible = collapsible; + self + } + + /// Show title bar on top of the window? + /// If `false`, the window will not be collapsible nor have a close-button. + pub fn title_bar(mut self, title_bar: bool) -> Self { + self.with_title_bar = title_bar; + self + } + + /// Not resizable, just takes the size of its contents. + /// Also disabled scrolling. + /// Text will not wrap, but will instead make your window width expand. + pub fn auto_sized(mut self) -> Self { + self.resize = self.resize.auto_sized(); + self.scroll = ScrollArea::neither(); + self + } + + /// Enable/disable horizontal/vertical scrolling. `false` by default. + pub fn scroll2(mut self, scroll: [bool; 2]) -> Self { + self.scroll = self.scroll.scroll2(scroll); + self + } + + /// Enable/disable horizontal scrolling. `false` by default. + pub fn hscroll(mut self, hscroll: bool) -> Self { + self.scroll = self.scroll.hscroll(hscroll); + self + } + + /// Enable/disable vertical scrolling. `false` by default. + pub fn vscroll(mut self, vscroll: bool) -> Self { + self.scroll = self.scroll.vscroll(vscroll); + self + } + + /// Constrain the area up to which the window can be dragged. + pub fn drag_bounds(mut self, bounds: Rect) -> Self { + self.area = self.area.drag_bounds(bounds); + self + } +} + +impl<'open> Window<'open> { + /// Returns `None` if the window is not open (if [`Window::open`] was called with `&mut false`). + /// Returns `Some(InnerResponse { inner: None })` if the window is collapsed. + #[inline] + pub fn show( + self, + ctx: &Context, + add_contents: impl FnOnce(&mut Ui) -> R, + ) -> Option>> { + self.show_dyn(ctx, Box::new(add_contents)) + } + + fn show_dyn<'c, R>( + self, + ctx: &Context, + add_contents: Box R + 'c>, + ) -> Option>> { + let Window { + title, + open, + area, + frame, + resize, + scroll, + collapsible, + default_open, + with_title_bar, + } = self; + + let frame = frame.unwrap_or_else(|| Frame::window(&ctx.style())); + + let is_explicitly_closed = matches!(open, Some(false)); + let is_open = !is_explicitly_closed || ctx.memory(|mem| mem.everything_is_visible()); + area.show_open_close_animation(ctx, &frame, is_open); + + if !is_open { + return None; + } + + let area_id = area.id; + let area_layer_id = area.layer(); + let resize_id = area_id.with("resize"); + let mut collapsing = + CollapsingState::load_with_default_open(ctx, area_id.with("collapsing"), default_open); + + let is_collapsed = with_title_bar && !collapsing.is_open(); + let possible = PossibleInteractions::new(&area, &resize, is_collapsed); + + let area = area.movable(false); // We move it manually, or the area will move the window when we want to resize it + let resize = resize.resizable(false); // We move it manually + let mut resize = resize.id(resize_id); + + let mut area = area.begin(ctx); + + let title_content_spacing = 2.0 * ctx.style().spacing.item_spacing.y; + + // First interact (move etc) to avoid frame delay: + let last_frame_outer_rect = area.state().rect(); + let interaction = if possible.movable || possible.resizable() { + window_interaction( + ctx, + possible, + area_layer_id, + area_id.with("frame_resize"), + last_frame_outer_rect, + ) + .and_then(|window_interaction| { + // Calculate roughly how much larger the window size is compared to the inner rect + let title_bar_height = if with_title_bar { + let style = ctx.style(); + ctx.fonts(|f| title.font_height(f, &style)) + title_content_spacing + } else { + 0.0 + }; + let margins = frame.outer_margin.sum() + + frame.inner_margin.sum() + + vec2(0.0, title_bar_height); + + interact( + window_interaction, + ctx, + margins, + area_layer_id, + &mut area, + resize_id, + ) + }) + } else { + None + }; + let hover_interaction = resize_hover(ctx, possible, area_layer_id, last_frame_outer_rect); + + let mut area_content_ui = area.content_ui(ctx); + + let content_inner = { + // BEGIN FRAME -------------------------------- + let frame_stroke = frame.stroke; + let mut frame = frame.begin(&mut area_content_ui); + + let show_close_button = open.is_some(); + let title_bar = if with_title_bar { + let title_bar = show_title_bar( + &mut frame.content_ui, + title, + show_close_button, + &mut collapsing, + collapsible, + ); + resize.min_size.x = resize.min_size.x.at_least(title_bar.rect.width()); // Prevent making window smaller than title bar width + Some(title_bar) + } else { + None + }; + + let (content_inner, content_response) = collapsing + .show_body_unindented(&mut frame.content_ui, |ui| { + resize.show(ui, |ui| { + if title_bar.is_some() { + ui.add_space(title_content_spacing); + } + + if scroll.has_any_bar() { + scroll.show(ui, add_contents).inner + } else { + add_contents(ui) + } + }) + }) + .map_or((None, None), |ir| (Some(ir.inner), Some(ir.response))); + + let outer_rect = frame.end(&mut area_content_ui).rect; + paint_resize_corner(&mut area_content_ui, &possible, outer_rect, frame_stroke); + + // END FRAME -------------------------------- + + if let Some(title_bar) = title_bar { + title_bar.ui( + &mut area_content_ui, + outer_rect, + &content_response, + open, + &mut collapsing, + collapsible, + ); + } + + collapsing.store(ctx); + + if let Some(interaction) = interaction { + paint_frame_interaction( + &mut area_content_ui, + outer_rect, + interaction, + ctx.style().visuals.widgets.active, + ); + } else if let Some(hover_interaction) = hover_interaction { + if ctx.input(|i| i.pointer.has_pointer()) { + paint_frame_interaction( + &mut area_content_ui, + outer_rect, + hover_interaction, + ctx.style().visuals.widgets.hovered, + ); + } + } + content_inner + }; + + { + let pos = ctx + .constrain_window_rect_to_area(area.state().rect(), area.drag_bounds()) + .left_top(); + area.state_mut().set_left_top_pos(pos); + } + + let full_response = area.end(ctx, area_content_ui); + + let inner_response = InnerResponse { + inner: content_inner, + response: full_response, + }; + Some(inner_response) + } +} + +fn paint_resize_corner( + ui: &mut Ui, + possible: &PossibleInteractions, + outer_rect: Rect, + stroke: Stroke, +) { + let corner = if possible.resize_right && possible.resize_bottom { + Align2::RIGHT_BOTTOM + } else if possible.resize_left && possible.resize_bottom { + Align2::LEFT_BOTTOM + } else if possible.resize_left && possible.resize_top { + Align2::LEFT_TOP + } else if possible.resize_right && possible.resize_top { + Align2::RIGHT_TOP + } else { + return; + }; + + let corner_size = Vec2::splat(ui.visuals().resize_corner_size); + let corner_rect = corner.align_size_within_rect(corner_size, outer_rect); + let corner_rect = corner_rect.translate(-2.0 * corner.to_sign()); // move away from corner + crate::resize::paint_resize_corner_with_style(ui, &corner_rect, stroke, corner); +} + +// ---------------------------------------------------------------------------- + +#[derive(Clone, Copy, Debug)] +struct PossibleInteractions { + movable: bool, + // Which sides can we drag to resize? + resize_left: bool, + resize_right: bool, + resize_top: bool, + resize_bottom: bool, +} + +impl PossibleInteractions { + fn new(area: &Area, resize: &Resize, is_collapsed: bool) -> Self { + let movable = area.is_enabled() && area.is_movable(); + let resizable = area.is_enabled() && resize.is_resizable() && !is_collapsed; + let pivot = area.get_pivot(); + Self { + movable, + resize_left: resizable && (movable || pivot.x() != Align::LEFT), + resize_right: resizable && (movable || pivot.x() != Align::RIGHT), + resize_top: resizable && (movable || pivot.y() != Align::TOP), + resize_bottom: resizable && (movable || pivot.y() != Align::BOTTOM), + } + } + + pub fn resizable(&self) -> bool { + self.resize_left || self.resize_right || self.resize_top || self.resize_bottom + } +} + +/// Either a move or resize +#[derive(Clone, Copy, Debug)] +pub(crate) struct WindowInteraction { + pub(crate) area_layer_id: LayerId, + pub(crate) start_rect: Rect, + pub(crate) left: bool, + pub(crate) right: bool, + pub(crate) top: bool, + pub(crate) bottom: bool, +} + +impl WindowInteraction { + pub fn set_cursor(&self, ctx: &Context) { + if (self.left && self.top) || (self.right && self.bottom) { + ctx.set_cursor_icon(CursorIcon::ResizeNwSe); + } else if (self.right && self.top) || (self.left && self.bottom) { + ctx.set_cursor_icon(CursorIcon::ResizeNeSw); + } else if self.left || self.right { + ctx.set_cursor_icon(CursorIcon::ResizeHorizontal); + } else if self.bottom || self.top { + ctx.set_cursor_icon(CursorIcon::ResizeVertical); + } + } + + pub fn is_resize(&self) -> bool { + self.left || self.right || self.top || self.bottom + } +} + +fn interact( + window_interaction: WindowInteraction, + ctx: &Context, + margins: Vec2, + area_layer_id: LayerId, + area: &mut area::Prepared, + resize_id: Id, +) -> Option { + let new_rect = move_and_resize_window(ctx, &window_interaction)?; + let new_rect = ctx.round_rect_to_pixels(new_rect); + + let new_rect = ctx.constrain_window_rect_to_area(new_rect, area.drag_bounds()); + + // TODO(emilk): add this to a Window state instead as a command "move here next frame" + area.state_mut().set_left_top_pos(new_rect.left_top()); + + if window_interaction.is_resize() { + if let Some(mut state) = resize::State::load(ctx, resize_id) { + state.requested_size = Some(new_rect.size() - margins); + state.store(ctx, resize_id); + } + } + + ctx.memory_mut(|mem| mem.areas.move_to_top(area_layer_id)); + Some(window_interaction) +} + +fn move_and_resize_window(ctx: &Context, window_interaction: &WindowInteraction) -> Option { + window_interaction.set_cursor(ctx); + + // Only move/resize windows with primary mouse button: + if !ctx.input(|i| i.pointer.primary_down()) { + return None; + } + + let pointer_pos = ctx.input(|i| i.pointer.interact_pos())?; + let mut rect = window_interaction.start_rect; // prevent drift + + if window_interaction.is_resize() { + if window_interaction.left { + rect.min.x = ctx.round_to_pixel(pointer_pos.x); + } else if window_interaction.right { + rect.max.x = ctx.round_to_pixel(pointer_pos.x); + } + + if window_interaction.top { + rect.min.y = ctx.round_to_pixel(pointer_pos.y); + } else if window_interaction.bottom { + rect.max.y = ctx.round_to_pixel(pointer_pos.y); + } + } else { + // Movement. + + // We do window interaction first (to avoid frame delay), + // but we want anything interactive in the window (e.g. slider) to steal + // the drag from us. It is therefor important not to move the window the first frame, + // but instead let other widgets to the steal. HACK. + if !ctx.input(|i| i.pointer.any_pressed()) { + let press_origin = ctx.input(|i| i.pointer.press_origin())?; + let delta = pointer_pos - press_origin; + rect = rect.translate(delta); + } + } + + Some(rect) +} + +/// Returns `Some` if there is a move or resize +fn window_interaction( + ctx: &Context, + possible: PossibleInteractions, + area_layer_id: LayerId, + id: Id, + rect: Rect, +) -> Option { + { + let drag_id = ctx.memory(|mem| mem.interaction.drag_id); + + if drag_id.is_some() && drag_id != Some(id) { + return None; + } + } + + let mut window_interaction = ctx.memory(|mem| mem.window_interaction); + + if window_interaction.is_none() { + if let Some(hover_window_interaction) = resize_hover(ctx, possible, area_layer_id, rect) { + hover_window_interaction.set_cursor(ctx); + if ctx.input(|i| i.pointer.any_pressed() && i.pointer.primary_down()) { + ctx.memory_mut(|mem| { + mem.interaction.drag_id = Some(id); + mem.interaction.drag_is_window = true; + window_interaction = Some(hover_window_interaction); + mem.window_interaction = window_interaction; + }); + } + } + } + + if let Some(window_interaction) = window_interaction { + let is_active = ctx.memory_mut(|mem| mem.interaction.drag_id == Some(id)); + + if is_active && window_interaction.area_layer_id == area_layer_id { + return Some(window_interaction); + } + } + + None +} + +fn resize_hover( + ctx: &Context, + possible: PossibleInteractions, + area_layer_id: LayerId, + rect: Rect, +) -> Option { + let pointer = ctx.input(|i| i.pointer.interact_pos())?; + + if ctx.input(|i| i.pointer.any_down() && !i.pointer.any_pressed()) { + return None; // already dragging (something) + } + + if let Some(top_layer_id) = ctx.layer_id_at(pointer) { + if top_layer_id != area_layer_id && top_layer_id.order != Order::Background { + return None; // Another window is on top here + } + } + + if ctx.memory(|mem| mem.interaction.drag_interest) { + // Another widget will become active if we drag here + return None; + } + + let side_grab_radius = ctx.style().interaction.resize_grab_radius_side; + let corner_grab_radius = ctx.style().interaction.resize_grab_radius_corner; + if !rect.expand(side_grab_radius).contains(pointer) { + return None; + } + + let mut left = possible.resize_left && (rect.left() - pointer.x).abs() <= side_grab_radius; + let mut right = possible.resize_right && (rect.right() - pointer.x).abs() <= side_grab_radius; + let mut top = possible.resize_top && (rect.top() - pointer.y).abs() <= side_grab_radius; + let mut bottom = + possible.resize_bottom && (rect.bottom() - pointer.y).abs() <= side_grab_radius; + + if possible.resize_right + && possible.resize_bottom + && rect.right_bottom().distance(pointer) < corner_grab_radius + { + right = true; + bottom = true; + } + if possible.resize_right + && possible.resize_top + && rect.right_top().distance(pointer) < corner_grab_radius + { + right = true; + top = true; + } + if possible.resize_left + && possible.resize_top + && rect.left_top().distance(pointer) < corner_grab_radius + { + left = true; + top = true; + } + if possible.resize_left + && possible.resize_bottom + && rect.left_bottom().distance(pointer) < corner_grab_radius + { + left = true; + bottom = true; + } + + let any_resize = left || right || top || bottom; + + if !any_resize && !possible.movable { + return None; + } + + if any_resize || possible.movable { + Some(WindowInteraction { + area_layer_id, + start_rect: rect, + left, + right, + top, + bottom, + }) + } else { + None + } +} + +/// Fill in parts of the window frame when we resize by dragging that part +fn paint_frame_interaction( + ui: &mut Ui, + rect: Rect, + interaction: WindowInteraction, + visuals: style::WidgetVisuals, +) { + use epaint::tessellator::path::add_circle_quadrant; + + let rounding = ui.visuals().window_rounding; + let Rect { min, max } = rect; + + let mut points = Vec::new(); + + if interaction.right && !interaction.bottom && !interaction.top { + points.push(pos2(max.x, min.y + rounding.ne)); + points.push(pos2(max.x, max.y - rounding.se)); + } + if interaction.right && interaction.bottom { + points.push(pos2(max.x, min.y + rounding.ne)); + points.push(pos2(max.x, max.y - rounding.se)); + add_circle_quadrant( + &mut points, + pos2(max.x - rounding.se, max.y - rounding.se), + rounding.se, + 0.0, + ); + } + if interaction.bottom { + points.push(pos2(max.x - rounding.se, max.y)); + points.push(pos2(min.x + rounding.sw, max.y)); + } + if interaction.left && interaction.bottom { + add_circle_quadrant( + &mut points, + pos2(min.x + rounding.sw, max.y - rounding.sw), + rounding.sw, + 1.0, + ); + } + if interaction.left { + points.push(pos2(min.x, max.y - rounding.sw)); + points.push(pos2(min.x, min.y + rounding.nw)); + } + if interaction.left && interaction.top { + add_circle_quadrant( + &mut points, + pos2(min.x + rounding.nw, min.y + rounding.nw), + rounding.nw, + 2.0, + ); + } + if interaction.top { + points.push(pos2(min.x + rounding.nw, min.y)); + points.push(pos2(max.x - rounding.ne, min.y)); + } + if interaction.right && interaction.top { + add_circle_quadrant( + &mut points, + pos2(max.x - rounding.ne, min.y + rounding.ne), + rounding.ne, + 3.0, + ); + points.push(pos2(max.x, min.y + rounding.ne)); + points.push(pos2(max.x, max.y - rounding.se)); + } + ui.painter().add(Shape::line(points, visuals.bg_stroke)); +} + +// ---------------------------------------------------------------------------- + +struct TitleBar { + /// A title Id used for dragging windows + id: Id, + + /// Prepared text in the title + title_galley: WidgetTextGalley, + + /// Size of the title bar in a collapsed state (if window is collapsible), + /// which includes all necessary space for showing the expand button, the + /// title and the close button. + min_rect: Rect, + + /// Size of the title bar in an expanded state. This size become known only + /// after expanding window and painting its content + rect: Rect, +} + +fn show_title_bar( + ui: &mut Ui, + title: WidgetText, + show_close_button: bool, + collapsing: &mut CollapsingState, + collapsible: bool, +) -> TitleBar { + let inner_response = ui.horizontal(|ui| { + let height = ui + .fonts(|fonts| title.font_height(fonts, ui.style())) + .max(ui.spacing().interact_size.y); + ui.set_min_height(height); + + let item_spacing = ui.spacing().item_spacing; + let button_size = Vec2::splat(ui.spacing().icon_width); + + let pad = (height - button_size.y) / 2.0; // calculated so that the icon is on the diagonal (if window padding is symmetrical) + + if collapsible { + ui.add_space(pad); + collapsing.show_default_button_with_size(ui, button_size); + } + + let title_galley = title.into_galley(ui, Some(false), f32::INFINITY, TextStyle::Heading); + + let minimum_width = if collapsible || show_close_button { + // If at least one button is shown we make room for both buttons (since title is centered): + 2.0 * (pad + button_size.x + item_spacing.x) + title_galley.size().x + } else { + pad + title_galley.size().x + pad + }; + let min_rect = Rect::from_min_size(ui.min_rect().min, vec2(minimum_width, height)); + let id = ui.advance_cursor_after_rect(min_rect); + + TitleBar { + id, + title_galley, + min_rect, + rect: Rect::NAN, // Will be filled in later + } + }); + + let title_bar = inner_response.inner; + let rect = inner_response.response.rect; + + TitleBar { rect, ..title_bar } +} + +impl TitleBar { + /// Finishes painting of the title bar when the window content size already known. + /// + /// # Parameters + /// + /// - `ui`: + /// - `outer_rect`: + /// - `content_response`: if `None`, window is collapsed at this frame, otherwise contains + /// a result of rendering the window content + /// - `open`: if `None`, no "Close" button will be rendered, otherwise renders and processes + /// the "Close" button and writes a `false` if window was closed + /// - `collapsing`: holds the current expanding state. Can be changed by double click on the + /// title if `collapsible` is `true` + /// - `collapsible`: if `true`, double click on the title bar will be handled for a change + /// of `collapsing` state + fn ui( + mut self, + ui: &mut Ui, + outer_rect: Rect, + content_response: &Option, + open: Option<&mut bool>, + collapsing: &mut CollapsingState, + collapsible: bool, + ) { + if let Some(content_response) = &content_response { + // Now we know how large we got to be: + self.rect.max.x = self.rect.max.x.max(content_response.rect.max.x); + } + + if let Some(open) = open { + // Add close button now that we know our full width: + if self.close_button_ui(ui).clicked() { + *open = false; + } + } + + let full_top_rect = Rect::from_x_y_ranges(self.rect.x_range(), self.min_rect.y_range()); + let text_pos = + emath::align::center_size_in_rect(self.title_galley.size(), full_top_rect).left_top(); + let text_pos = text_pos - self.title_galley.galley().rect.min.to_vec2(); + let text_pos = text_pos - 1.5 * Vec2::Y; // HACK: center on x-height of text (looks better) + self.title_galley.paint_with_fallback_color( + ui.painter(), + text_pos, + ui.visuals().text_color(), + ); + + if let Some(content_response) = &content_response { + // paint separator between title and content: + let y = content_response.rect.top() + ui.spacing().item_spacing.y * 0.5; + // let y = lerp(self.rect.bottom()..=content_response.rect.top(), 0.5); + let stroke = ui.visuals().widgets.noninteractive.bg_stroke; + ui.painter().hline(outer_rect.x_range(), y, stroke); + } + + // Don't cover the close- and collapse buttons: + let double_click_rect = self.rect.shrink2(vec2(32.0, 0.0)); + + if ui + .interact(double_click_rect, self.id, Sense::click()) + .double_clicked() + && collapsible + { + collapsing.toggle(ui); + } + } + + /// Paints the "Close" button at the right side of the title bar + /// and processes clicks on it. + /// + /// The button is square and its size is determined by the + /// [`crate::style::Spacing::icon_width`] setting. + fn close_button_ui(&self, ui: &mut Ui) -> Response { + let button_size = Vec2::splat(ui.spacing().icon_width); + let pad = (self.rect.height() - button_size.y) / 2.0; // calculated so that the icon is on the diagonal (if window padding is symmetrical) + let button_rect = Rect::from_min_size( + pos2( + self.rect.right() - pad - button_size.x, + self.rect.center().y - 0.5 * button_size.y, + ), + button_size, + ); + + close_button(ui, button_rect) + } +} + +/// Paints the "Close" button of the window and processes clicks on it. +/// +/// The close button is just an `X` symbol painted by a current stroke +/// for foreground elements (such as a label text). +/// +/// # Parameters +/// - `ui`: +/// - `rect`: The rectangular area to fit the button in +/// +/// Returns the result of a click on a button if it was pressed +fn close_button(ui: &mut Ui, rect: Rect) -> Response { + let close_id = ui.auto_id_with("window_close_button"); + let response = ui.interact(rect, close_id, Sense::click()); + ui.expand_to_include_rect(response.rect); + + let visuals = ui.style().interact(&response); + let rect = rect.shrink(2.0).expand(visuals.expansion); + let stroke = visuals.fg_stroke; + ui.painter() // paints \ + .line_segment([rect.left_top(), rect.right_bottom()], stroke); + ui.painter() // paints / + .line_segment([rect.right_top(), rect.left_bottom()], stroke); + response +} diff --git a/nevmes-gui/crates/egui/src/context.rs b/nevmes-gui/crates/egui/src/context.rs new file mode 100644 index 0000000..21a79ab --- /dev/null +++ b/nevmes-gui/crates/egui/src/context.rs @@ -0,0 +1,1799 @@ +// #![warn(missing_docs)] +use std::sync::Arc; + +use crate::{ + animation_manager::AnimationManager, data::output::PlatformOutput, frame_state::FrameState, + input_state::*, layers::GraphicLayers, memory::Options, os::OperatingSystem, + output::FullOutput, util::IdTypeMap, TextureHandle, *, +}; +use epaint::{mutex::*, stats::*, text::Fonts, TessellationOptions, *}; + +// ---------------------------------------------------------------------------- + +struct WrappedTextureManager(Arc>); + +impl Default for WrappedTextureManager { + fn default() -> Self { + let mut tex_mngr = epaint::textures::TextureManager::default(); + + // Will be filled in later + let font_id = tex_mngr.alloc( + "egui_font_texture".into(), + epaint::FontImage::new([0, 0]).into(), + Default::default(), + ); + assert_eq!(font_id, TextureId::default()); + + Self(Arc::new(RwLock::new(tex_mngr))) + } +} + +// ---------------------------------------------------------------------------- +#[derive(Default)] +struct ContextImpl { + /// `None` until the start of the first frame. + fonts: Option, + memory: Memory, + animation_manager: AnimationManager, + tex_manager: WrappedTextureManager, + + os: OperatingSystem, + + input: InputState, + + /// State that is collected during a frame and then cleared + frame_state: FrameState, + + // The output of a frame: + graphics: GraphicLayers, + output: PlatformOutput, + + paint_stats: PaintStats, + + /// the duration backend will poll for new events, before forcing another egui update + /// even if there's no new events. + repaint_after: std::time::Duration, + + /// While positive, keep requesting repaints. Decrement at the end of each frame. + repaint_requests: u32, + request_repaint_callback: Option>, + + /// used to suppress multiple calls to [`Self::request_repaint_callback`] during the same frame. + has_requested_repaint_this_frame: bool, + + requested_repaint_last_frame: bool, + + /// Written to during the frame. + layer_rects_this_frame: ahash::HashMap>, + + /// Read + layer_rects_prev_frame: ahash::HashMap>, + + #[cfg(feature = "accesskit")] + is_accesskit_enabled: bool, + #[cfg(feature = "accesskit")] + accesskit_node_classes: accesskit::NodeClassSet, +} + +impl ContextImpl { + fn begin_frame_mut(&mut self, mut new_raw_input: RawInput) { + self.has_requested_repaint_this_frame = false; // allow new calls during the frame + + if let Some(new_pixels_per_point) = self.memory.new_pixels_per_point.take() { + new_raw_input.pixels_per_point = Some(new_pixels_per_point); + + // This is a bit hacky, but is required to avoid jitter: + let ratio = self.input.pixels_per_point / new_pixels_per_point; + let mut rect = self.input.screen_rect; + rect.min = (ratio * rect.min.to_vec2()).to_pos2(); + rect.max = (ratio * rect.max.to_vec2()).to_pos2(); + new_raw_input.screen_rect = Some(rect); + } + + self.layer_rects_prev_frame = std::mem::take(&mut self.layer_rects_this_frame); + + self.memory.begin_frame(&self.input, &new_raw_input); + + self.input = std::mem::take(&mut self.input) + .begin_frame(new_raw_input, self.requested_repaint_last_frame); + + self.frame_state.begin_frame(&self.input); + + self.update_fonts_mut(); + + // Ensure we register the background area so panels and background ui can catch clicks: + let screen_rect = self.input.screen_rect(); + self.memory.areas.set_state( + LayerId::background(), + containers::area::State { + pivot_pos: screen_rect.left_top(), + pivot: Align2::LEFT_TOP, + size: screen_rect.size(), + interactable: true, + }, + ); + + #[cfg(feature = "accesskit")] + if self.is_accesskit_enabled { + use crate::frame_state::AccessKitFrameState; + let id = crate::accesskit_root_id(); + let mut builder = accesskit::NodeBuilder::new(accesskit::Role::Window); + builder.set_transform(accesskit::Affine::scale( + self.input.pixels_per_point().into(), + )); + let mut node_builders = IdMap::default(); + node_builders.insert(id, builder); + self.frame_state.accesskit_state = Some(AccessKitFrameState { + node_builders, + parent_stack: vec![id], + }); + } + } + + /// Load fonts unless already loaded. + fn update_fonts_mut(&mut self) { + let pixels_per_point = self.input.pixels_per_point(); + let max_texture_side = self.input.max_texture_side; + + if let Some(font_definitions) = self.memory.new_font_definitions.take() { + let fonts = Fonts::new(pixels_per_point, max_texture_side, font_definitions); + self.fonts = Some(fonts); + } + + let fonts = self.fonts.get_or_insert_with(|| { + let font_definitions = FontDefinitions::default(); + Fonts::new(pixels_per_point, max_texture_side, font_definitions) + }); + + fonts.begin_frame(pixels_per_point, max_texture_side); + + if self.memory.options.preload_font_glyphs { + // Preload the most common characters for the most common fonts. + // This is not very important to do, but may a few GPU operations. + for font_id in self.memory.options.style.text_styles.values() { + fonts.lock().fonts.font(font_id).preload_common_characters(); + } + } + } + + #[cfg(feature = "accesskit")] + fn accesskit_node_builder(&mut self, id: Id) -> &mut accesskit::NodeBuilder { + let state = self.frame_state.accesskit_state.as_mut().unwrap(); + let builders = &mut state.node_builders; + if let std::collections::hash_map::Entry::Vacant(entry) = builders.entry(id) { + entry.insert(Default::default()); + let parent_id = state.parent_stack.last().unwrap(); + let parent_builder = builders.get_mut(parent_id).unwrap(); + parent_builder.push_child(id.accesskit_id()); + } + builders.get_mut(&id).unwrap() + } +} + +// ---------------------------------------------------------------------------- + +/// Your handle to egui. +/// +/// This is the first thing you need when working with egui. +/// Contains the [`InputState`], [`Memory`], [`PlatformOutput`], and more. +/// +/// [`Context`] is cheap to clone, and any clones refers to the same mutable data +/// ([`Context`] uses refcounting internally). +/// +/// ## Locking +/// All methods are marked `&self`; [`Context`] has interior mutability protected by an [`RwLock`]. +/// +/// To access parts of a `Context` you need to use some of the helper functions that take closures: +/// +/// ``` +/// # let ctx = egui::Context::default(); +/// if ctx.input(|i| i.key_pressed(egui::Key::A)) { +/// ctx.output_mut(|o| o.copied_text = "Hello!".to_string()); +/// } +/// ``` +/// +/// Within such a closure you may NOT recursively lock the same [`Context`], as that can lead to a deadlock. +/// Therefore it is important that any lock of [`Context`] is short-lived. +/// +/// These are effectively transactional accesses. +/// +/// [`Ui`] has many of the same accessor functions, and the same applies there. +/// +/// ## Example: +/// +/// ``` no_run +/// # fn handle_platform_output(_: egui::PlatformOutput) {} +/// # fn paint(textures_detla: egui::TexturesDelta, _: Vec) {} +/// let mut ctx = egui::Context::default(); +/// +/// // Game loop: +/// loop { +/// let raw_input = egui::RawInput::default(); +/// let full_output = ctx.run(raw_input, |ctx| { +/// egui::CentralPanel::default().show(&ctx, |ui| { +/// ui.label("Hello world!"); +/// if ui.button("Click me").clicked() { +/// // take some action here +/// } +/// }); +/// }); +/// handle_platform_output(full_output.platform_output); +/// let clipped_primitives = ctx.tessellate(full_output.shapes); // create triangles to paint +/// paint(full_output.textures_delta, clipped_primitives); +/// } +/// ``` +#[derive(Clone)] +pub struct Context(Arc>); + +impl std::fmt::Debug for Context { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Context").finish_non_exhaustive() + } +} + +impl std::cmp::PartialEq for Context { + fn eq(&self, other: &Context) -> bool { + Arc::ptr_eq(&self.0, &other.0) + } +} + +impl Default for Context { + fn default() -> Self { + Self(Arc::new(RwLock::new(ContextImpl { + // Start with painting an extra frame to compensate for some widgets + // that take two frames before they "settle": + repaint_requests: 1, + ..ContextImpl::default() + }))) + } +} + +impl Context { + // Do read-only (shared access) transaction on Context + fn read(&self, reader: impl FnOnce(&ContextImpl) -> R) -> R { + reader(&self.0.read()) + } + + // Do read-write (exclusive access) transaction on Context + fn write(&self, writer: impl FnOnce(&mut ContextImpl) -> R) -> R { + writer(&mut self.0.write()) + } + + /// Run the ui code for one frame. + /// + /// Put your widgets into a [`SidePanel`], [`TopBottomPanel`], [`CentralPanel`], [`Window`] or [`Area`]. + /// + /// This will modify the internal reference to point to a new generation of [`Context`]. + /// Any old clones of this [`Context`] will refer to the old [`Context`], which will not get new input. + /// + /// You can alternatively run [`Self::begin_frame`] and [`Context::end_frame`]. + /// + /// ``` + /// // One egui context that you keep reusing: + /// let mut ctx = egui::Context::default(); + /// + /// // Each frame: + /// let input = egui::RawInput::default(); + /// let full_output = ctx.run(input, |ctx| { + /// egui::CentralPanel::default().show(&ctx, |ui| { + /// ui.label("Hello egui!"); + /// }); + /// }); + /// // handle full_output + /// ``` + #[must_use] + pub fn run(&self, new_input: RawInput, run_ui: impl FnOnce(&Context)) -> FullOutput { + self.begin_frame(new_input); + run_ui(self); + self.end_frame() + } + + /// An alternative to calling [`Self::run`]. + /// + /// ``` + /// // One egui context that you keep reusing: + /// let mut ctx = egui::Context::default(); + /// + /// // Each frame: + /// let input = egui::RawInput::default(); + /// ctx.begin_frame(input); + /// + /// egui::CentralPanel::default().show(&ctx, |ui| { + /// ui.label("Hello egui!"); + /// }); + /// + /// let full_output = ctx.end_frame(); + /// // handle full_output + /// ``` + pub fn begin_frame(&self, new_input: RawInput) { + self.write(|ctx| ctx.begin_frame_mut(new_input)); + } +} + +/// ## Borrows parts of [`Context`] +/// These functions all lock the [`Context`]. +/// Please see the documentation of [`Context`] for how locking works! +impl Context { + /// Read-only access to [`InputState`]. + /// + /// Note that this locks the [`Context`]. + /// + /// ``` + /// # let mut ctx = egui::Context::default(); + /// ctx.input(|i| { + /// // ⚠️ Using `ctx` (even from other `Arc` reference) again here will lead to a dead-lock! + /// }); + /// + /// if let Some(pos) = ctx.input(|i| i.pointer.hover_pos()) { + /// // This is fine! + /// } + /// ``` + #[inline] + pub fn input(&self, reader: impl FnOnce(&InputState) -> R) -> R { + self.read(move |ctx| reader(&ctx.input)) + } + + /// Read-write access to [`InputState`]. + #[inline] + pub fn input_mut(&self, writer: impl FnOnce(&mut InputState) -> R) -> R { + self.write(move |ctx| writer(&mut ctx.input)) + } + + /// Read-only access to [`Memory`]. + #[inline] + pub fn memory(&self, reader: impl FnOnce(&Memory) -> R) -> R { + self.read(move |ctx| reader(&ctx.memory)) + } + + /// Read-write access to [`Memory`]. + #[inline] + pub fn memory_mut(&self, writer: impl FnOnce(&mut Memory) -> R) -> R { + self.write(move |ctx| writer(&mut ctx.memory)) + } + + /// Read-only access to [`IdTypeMap`], which stores superficial widget state. + #[inline] + pub fn data(&self, reader: impl FnOnce(&IdTypeMap) -> R) -> R { + self.read(move |ctx| reader(&ctx.memory.data)) + } + + /// Read-write access to [`IdTypeMap`], which stores superficial widget state. + #[inline] + pub fn data_mut(&self, writer: impl FnOnce(&mut IdTypeMap) -> R) -> R { + self.write(move |ctx| writer(&mut ctx.memory.data)) + } + + /// Read-write access to [`GraphicLayers`], where painted [`crate::Shape`]s are written to. + #[inline] + pub(crate) fn graphics_mut(&self, writer: impl FnOnce(&mut GraphicLayers) -> R) -> R { + self.write(move |ctx| writer(&mut ctx.graphics)) + } + + /// Read-only access to [`PlatformOutput`]. + /// + /// This is what egui outputs each frame. + /// + /// ``` + /// # let mut ctx = egui::Context::default(); + /// ctx.output_mut(|o| o.cursor_icon = egui::CursorIcon::Progress); + /// ``` + #[inline] + pub fn output(&self, reader: impl FnOnce(&PlatformOutput) -> R) -> R { + self.read(move |ctx| reader(&ctx.output)) + } + + /// Read-write access to [`PlatformOutput`]. + #[inline] + pub fn output_mut(&self, writer: impl FnOnce(&mut PlatformOutput) -> R) -> R { + self.write(move |ctx| writer(&mut ctx.output)) + } + + /// Read-only access to [`FrameState`]. + #[inline] + pub(crate) fn frame_state(&self, reader: impl FnOnce(&FrameState) -> R) -> R { + self.read(move |ctx| reader(&ctx.frame_state)) + } + + /// Read-write access to [`FrameState`]. + #[inline] + pub(crate) fn frame_state_mut(&self, writer: impl FnOnce(&mut FrameState) -> R) -> R { + self.write(move |ctx| writer(&mut ctx.frame_state)) + } + + /// Read-only access to [`Fonts`]. + /// + /// Not valid until first call to [`Context::run()`]. + /// That's because since we don't know the proper `pixels_per_point` until then. + #[inline] + pub fn fonts(&self, reader: impl FnOnce(&Fonts) -> R) -> R { + self.read(move |ctx| { + reader( + ctx.fonts + .as_ref() + .expect("No fonts available until first call to Context::run()"), + ) + }) + } + + /// Read-write access to [`Fonts`]. + #[inline] + pub fn fonts_mut(&self, writer: impl FnOnce(&mut Option) -> R) -> R { + self.write(move |ctx| writer(&mut ctx.fonts)) + } + + /// Read-only access to [`Options`]. + #[inline] + pub fn options(&self, reader: impl FnOnce(&Options) -> R) -> R { + self.read(move |ctx| reader(&ctx.memory.options)) + } + + /// Read-write access to [`Options`]. + #[inline] + pub fn options_mut(&self, writer: impl FnOnce(&mut Options) -> R) -> R { + self.write(move |ctx| writer(&mut ctx.memory.options)) + } + + /// Read-only access to [`TessellationOptions`]. + #[inline] + pub fn tessellation_options(&self, reader: impl FnOnce(&TessellationOptions) -> R) -> R { + self.read(move |ctx| reader(&ctx.memory.options.tessellation_options)) + } + + /// Read-write access to [`TessellationOptions`]. + #[inline] + pub fn tessellation_options_mut( + &self, + writer: impl FnOnce(&mut TessellationOptions) -> R, + ) -> R { + self.write(move |ctx| writer(&mut ctx.memory.options.tessellation_options)) + } +} + +impl Context { + // --------------------------------------------------------------------- + + /// If the given [`Id`] has been used previously the same frame at at different position, + /// then an error will be printed on screen. + /// + /// This function is already called for all widgets that do any interaction, + /// but you can call this from widgets that store state but that does not interact. + /// + /// The given [`Rect`] should be approximately where the widget will be. + /// The most important thing is that [`Rect::min`] is approximately correct, + /// because that's where the warning will be painted. If you don't know what size to pick, just pick [`Vec2::ZERO`]. + pub fn check_for_id_clash(&self, id: Id, new_rect: Rect, what: &str) { + let prev_rect = self.frame_state_mut(move |state| state.used_ids.insert(id, new_rect)); + if let Some(prev_rect) = prev_rect { + // it is ok to reuse the same ID for e.g. a frame around a widget, + // or to check for interaction with the same widget twice: + if prev_rect.expand(0.1).contains_rect(new_rect) + || new_rect.expand(0.1).contains_rect(prev_rect) + { + return; + } + + let show_error = |widget_rect: Rect, text: String| { + let text = format!("🔥 {}", text); + let color = self.style().visuals.error_fg_color; + let painter = self.debug_painter(); + painter.rect_stroke(widget_rect, 0.0, (1.0, color)); + + let below = widget_rect.bottom() + 32.0 < self.input(|i| i.screen_rect.bottom()); + + let text_rect = if below { + painter.debug_text( + widget_rect.left_bottom() + vec2(0.0, 2.0), + Align2::LEFT_TOP, + color, + text, + ) + } else { + painter.debug_text( + widget_rect.left_top() - vec2(0.0, 2.0), + Align2::LEFT_BOTTOM, + color, + text, + ) + }; + + if let Some(pointer_pos) = self.pointer_hover_pos() { + if text_rect.contains(pointer_pos) { + let tooltip_pos = if below { + text_rect.left_bottom() + vec2(2.0, 4.0) + } else { + text_rect.left_top() + vec2(2.0, -4.0) + }; + + painter.error( + tooltip_pos, + format!("Widget is {} this text.\n\n\ + ID clashes happens when things like Windows or CollapsingHeaders share names,\n\ + or when things like Plot and Grid:s aren't given unique id_source:s.\n\n\ + Sometimes the solution is to use ui.push_id.", + if below { "above" } else { "below" }) + ); + } + } + }; + + let id_str = id.short_debug_format(); + + if prev_rect.min.distance(new_rect.min) < 4.0 { + show_error(new_rect, format!("Double use of {} ID {}", what, id_str)); + } else { + show_error(prev_rect, format!("First use of {} ID {}", what, id_str)); + show_error(new_rect, format!("Second use of {} ID {}", what, id_str)); + } + } + } + + // --------------------------------------------------------------------- + + /// Use `ui.interact` instead + #[allow(clippy::too_many_arguments)] + pub(crate) fn interact( + &self, + clip_rect: Rect, + item_spacing: Vec2, + layer_id: LayerId, + id: Id, + rect: Rect, + sense: Sense, + enabled: bool, + ) -> Response { + let gap = 0.1; // Just to make sure we don't accidentally hover two things at once (a small eps should be sufficient). + + // Make it easier to click things: + let interact_rect = rect.expand2( + (0.5 * item_spacing - Vec2::splat(gap)) + .at_least(Vec2::splat(0.0)) + .at_most(Vec2::splat(5.0)), + ); + + // Respect clip rectangle when interacting + let interact_rect = clip_rect.intersect(interact_rect); + let mut hovered = self.rect_contains_pointer(layer_id, interact_rect); + + // This solves the problem of overlapping widgets. + // Whichever widget is added LAST (=on top) gets the input: + if interact_rect.is_positive() && sense.interactive() { + if self.style().debug.show_interactive_widgets { + Self::layer_painter(self, LayerId::debug()).rect( + interact_rect, + 0.0, + Color32::YELLOW.additive().linear_multiply(0.005), + Stroke::new(1.0, Color32::YELLOW.additive().linear_multiply(0.05)), + ); + } + let mut show_blocking_widget = None; + + self.write(|ctx| { + ctx.layer_rects_this_frame + .entry(layer_id) + .or_default() + .push((id, interact_rect)); + + if hovered { + let pointer_pos = ctx.input.pointer.interact_pos(); + if let Some(pointer_pos) = pointer_pos { + if let Some(rects) = ctx.layer_rects_prev_frame.get(&layer_id) { + for &(prev_id, prev_rect) in rects.iter().rev() { + if prev_id == id { + break; // there is no other interactive widget covering us at the pointer position. + } + if prev_rect.contains(pointer_pos) { + // Another interactive widget is covering us at the pointer position, + // so we aren't hovered. + + if ctx.memory.options.style.debug.show_blocking_widget { + // Store the rects to use them outside the write() call to + // avoid deadlock + show_blocking_widget = Some((interact_rect, prev_rect)); + } + + hovered = false; + break; + } + } + } + } + } + }); + + if let Some((interact_rect, prev_rect)) = show_blocking_widget { + Self::layer_painter(self, LayerId::debug()).debug_rect( + interact_rect, + Color32::GREEN, + "Covered", + ); + Self::layer_painter(self, LayerId::debug()).debug_rect( + prev_rect, + Color32::LIGHT_BLUE, + "On top", + ); + } + } + + self.interact_with_hovered(layer_id, id, rect, sense, enabled, hovered) + } + + /// You specify if a thing is hovered, and the function gives a [`Response`]. + pub(crate) fn interact_with_hovered( + &self, + layer_id: LayerId, + id: Id, + rect: Rect, + sense: Sense, + enabled: bool, + hovered: bool, + ) -> Response { + let hovered = hovered && enabled; // can't even hover disabled widgets + + let highlighted = self.frame_state(|fs| fs.highlight_this_frame.contains(&id)); + + let mut response = Response { + ctx: self.clone(), + layer_id, + id, + rect, + sense, + enabled, + hovered, + highlighted, + clicked: Default::default(), + double_clicked: Default::default(), + triple_clicked: Default::default(), + dragged: false, + drag_released: false, + is_pointer_button_down_on: false, + interact_pointer_pos: None, + changed: false, // must be set by the widget itself + }; + + if !enabled || !sense.focusable || !layer_id.allow_interaction() { + // Not interested or allowed input: + self.memory_mut(|mem| mem.surrender_focus(id)); + return response; + } + + self.check_for_id_clash(id, rect, "widget"); + + #[cfg(feature = "accesskit")] + if sense.focusable { + // Make sure anything that can receive focus has an AccessKit node. + // TODO(mwcampbell): For nodes that are filled from widget info, + // some information is written to the node twice. + self.accesskit_node_builder(id, |builder| response.fill_accesskit_node_common(builder)); + } + + let clicked_elsewhere = response.clicked_elsewhere(); + self.write(|ctx| { + let memory = &mut ctx.memory; + let input = &mut ctx.input; + + if sense.focusable { + memory.interested_in_focus(id); + } + + if sense.click + && memory.has_focus(response.id) + && (input.key_pressed(Key::Space) || input.key_pressed(Key::Enter)) + { + // Space/enter works like a primary click for e.g. selected buttons + response.clicked[PointerButton::Primary as usize] = true; + } + + #[cfg(feature = "accesskit")] + { + if sense.click + && input.has_accesskit_action_request(response.id, accesskit::Action::Default) + { + response.clicked[PointerButton::Primary as usize] = true; + } + } + + if sense.click || sense.drag { + memory.interaction.click_interest |= hovered && sense.click; + memory.interaction.drag_interest |= hovered && sense.drag; + + response.dragged = memory.interaction.drag_id == Some(id); + response.is_pointer_button_down_on = + memory.interaction.click_id == Some(id) || response.dragged; + + for pointer_event in &input.pointer.pointer_events { + match pointer_event { + PointerEvent::Moved(_) => {} + PointerEvent::Pressed { .. } => { + if hovered { + if sense.click && memory.interaction.click_id.is_none() { + // potential start of a click + memory.interaction.click_id = Some(id); + response.is_pointer_button_down_on = true; + } + + // HACK: windows have low priority on dragging. + // This is so that if you drag a slider in a window, + // the slider will steal the drag away from the window. + // This is needed because we do window interaction first (to prevent frame delay), + // and then do content layout. + if sense.drag + && (memory.interaction.drag_id.is_none() + || memory.interaction.drag_is_window) + { + // potential start of a drag + memory.interaction.drag_id = Some(id); + memory.interaction.drag_is_window = false; + memory.window_interaction = None; // HACK: stop moving windows (if any) + response.is_pointer_button_down_on = true; + response.dragged = true; + } + } + } + PointerEvent::Released { click, button } => { + response.drag_released = response.dragged; + response.dragged = false; + + if hovered && response.is_pointer_button_down_on { + if let Some(click) = click { + let clicked = hovered && response.is_pointer_button_down_on; + response.clicked[*button as usize] = clicked; + response.double_clicked[*button as usize] = + clicked && click.is_double(); + response.triple_clicked[*button as usize] = + clicked && click.is_triple(); + } + } + } + } + } + } + + if response.is_pointer_button_down_on { + response.interact_pointer_pos = input.pointer.interact_pos(); + } + + if input.pointer.any_down() { + response.hovered &= response.is_pointer_button_down_on; // we don't hover widgets while interacting with *other* widgets + } + + if memory.has_focus(response.id) && clicked_elsewhere { + memory.surrender_focus(id); + } + + if response.dragged() && !memory.has_focus(response.id) { + // e.g.: remove focus from a widget when you drag something else + memory.stop_text_input(); + } + }); + + response + } + + /// Get a full-screen painter for a new or existing layer + pub fn layer_painter(&self, layer_id: LayerId) -> Painter { + let screen_rect = self.screen_rect(); + Painter::new(self.clone(), layer_id, screen_rect) + } + + /// Paint on top of everything else + pub fn debug_painter(&self) -> Painter { + Self::layer_painter(self, LayerId::debug()) + } + + /// What operating system are we running on? + /// + /// When compiling natively, this is + /// figured out from the `target_os`. + /// + /// For web, this can be figured out from the user-agent, + /// and is done so by [`eframe`](https://github.com/emilk/egui/tree/master/crates/eframe). + pub fn os(&self) -> OperatingSystem { + self.read(|ctx| ctx.os) + } + + /// Set the operating system we are running on. + /// + /// If you are writing wasm-based integration for egui you + /// may want to set this based on e.g. the user-agent. + pub fn set_os(&self, os: OperatingSystem) { + self.write(|ctx| ctx.os = os); + } + + /// Set the cursor icon. + /// + /// Equivalent to: + /// ``` + /// # let ctx = egui::Context::default(); + /// ctx.output_mut(|o| o.cursor_icon = egui::CursorIcon::PointingHand); + /// ``` + pub fn set_cursor_icon(&self, cursor_icon: CursorIcon) { + self.output_mut(|o| o.cursor_icon = cursor_icon); + } + + /// Format the given shortcut in a human-readable way (e.g. `Ctrl+Shift+X`). + /// + /// Can be used to get the text for [`Button::shortcut_text`]. + pub fn format_shortcut(&self, shortcut: &KeyboardShortcut) -> String { + let os = self.os(); + + let is_mac = matches!(os, OperatingSystem::Mac | OperatingSystem::IOS); + + let can_show_symbols = || { + let ModifierNames { + alt, + ctrl, + shift, + mac_cmd, + .. + } = ModifierNames::SYMBOLS; + + let font_id = TextStyle::Body.resolve(&self.style()); + self.fonts(|f| { + let mut lock = f.lock(); + let font = lock.fonts.font(&font_id); + font.has_glyphs(alt) + && font.has_glyphs(ctrl) + && font.has_glyphs(shift) + && font.has_glyphs(mac_cmd) + }) + }; + + if is_mac && can_show_symbols() { + shortcut.format(&ModifierNames::SYMBOLS, is_mac) + } else { + shortcut.format(&ModifierNames::NAMES, is_mac) + } + } + + /// Call this if there is need to repaint the UI, i.e. if you are showing an animation. + /// + /// If this is called at least once in a frame, then there will be another frame right after this. + /// Call as many times as you wish, only one repaint will be issued. + /// + /// If called from outside the UI thread, the UI thread will wake up and run, + /// provided the egui integration has set that up via [`Self::set_request_repaint_callback`] + /// (this will work on `eframe`). + pub fn request_repaint(&self) { + // request two frames of repaint, just to cover some corner cases (frame delays): + self.write(|ctx| { + ctx.repaint_requests = 2; + if let Some(callback) = &ctx.request_repaint_callback { + if !ctx.has_requested_repaint_this_frame { + (callback)(); + ctx.has_requested_repaint_this_frame = true; + } + } + }); + } + + /// Request repaint after the specified duration elapses in the case of no new input + /// events being received. + /// + /// The function can be multiple times, but only the *smallest* duration will be considered. + /// So, if the function is called two times with `1 second` and `2 seconds`, egui will repaint + /// after `1 second` + /// + /// This is primarily useful for applications who would like to save battery by avoiding wasted + /// redraws when the app is not in focus. But sometimes the GUI of the app might become stale + /// and outdated if it is not updated for too long. + /// + /// Lets say, something like a stop watch widget that displays the time in seconds. You would waste + /// resources repainting multiple times within the same second (when you have no input), + /// just calculate the difference of duration between current time and next second change, + /// and call this function, to make sure that you are displaying the latest updated time, but + /// not wasting resources on needless repaints within the same second. + /// + /// NOTE: only works if called before `Context::end_frame()`. to force egui to update, + /// use `Context::request_repaint()` instead. + /// + /// ### Quirk: + /// Duration begins at the next frame. lets say for example that its a very inefficient app + /// and takes 500 milliseconds per frame at 2 fps. The widget / user might want a repaint in + /// next 500 milliseconds. Now, app takes 1000 ms per frame (1 fps) because the backend event + /// timeout takes 500 milli seconds AFTER the vsync swap buffer. + /// So, its not that we are requesting repaint within X duration. We are rather timing out + /// during app idle time where we are not receiving any new input events. + pub fn request_repaint_after(&self, duration: std::time::Duration) { + // Maybe we can check if duration is ZERO, and call self.request_repaint()? + self.write(|ctx| ctx.repaint_after = ctx.repaint_after.min(duration)); + } + + /// For integrations: this callback will be called when an egui user calls [`Self::request_repaint`]. + /// + /// This lets you wake up a sleeping UI thread. + /// + /// Note that only one callback can be set. Any new call overrides the previous callback. + pub fn set_request_repaint_callback(&self, callback: impl Fn() + Send + Sync + 'static) { + let callback = Box::new(callback); + self.write(|ctx| ctx.request_repaint_callback = Some(callback)); + } + + /// Tell `egui` which fonts to use. + /// + /// The default `egui` fonts only support latin and cyrillic alphabets, + /// but you can call this to install additional fonts that support e.g. korean characters. + /// + /// The new fonts will become active at the start of the next frame. + pub fn set_fonts(&self, font_definitions: FontDefinitions) { + let update_fonts = self.fonts_mut(|fonts| { + if let Some(current_fonts) = fonts { + // NOTE: this comparison is expensive since it checks TTF data for equality + current_fonts.lock().fonts.definitions() != &font_definitions + } else { + true + } + }); + + if update_fonts { + self.memory_mut(|mem| mem.new_font_definitions = Some(font_definitions)); + } + } + + /// The [`Style`] used by all subsequent windows, panels etc. + pub fn style(&self) -> Arc