From cf05050c955e283c6e7435bec5921008b1e3e354 Mon Sep 17 00:00:00 2001 From: Schrottkatze Date: Tue, 17 Jun 2025 20:50:46 +0200 Subject: [PATCH] add debug console initial implementation --- Cargo.lock | 201 ++++++++++++++---- Cargo.toml | 3 + .../DepartureMono-Regular.otf | Bin 0 -> 84480 bytes assets/fonts/DepartureMono-1.500/LICENSE | 93 ++++++++ src/debugging.rs | 13 +- src/game.rs | 3 +- src/game/camera.rs | 2 +- src/game/debug.rs | 13 ++ src/game/debug/console.rs | 105 +++++++++ src/game/debug/console/cli.rs | 32 +++ src/game/debug/console/ui.rs | 112 ++++++++++ src/game/debug/console/ui/components.rs | 180 ++++++++++++++++ src/main.rs | 3 +- 13 files changed, 709 insertions(+), 51 deletions(-) create mode 100644 assets/fonts/DepartureMono-1.500/DepartureMono-Regular.otf create mode 100644 assets/fonts/DepartureMono-1.500/LICENSE create mode 100644 src/game/debug.rs create mode 100644 src/game/debug/console.rs create mode 100644 src/game/debug/console/cli.rs create mode 100644 src/game/debug/console/ui.rs create mode 100644 src/game/debug/console/ui/components.rs diff --git a/Cargo.lock b/Cargo.lock index d2eb367..67b85ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -283,7 +283,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -321,7 +321,7 @@ checksum = "f548ad2c4031f2902e3edc1f29c29e835829437de49562d8eb5dc5584d3a1043" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -538,7 +538,7 @@ checksum = "2656316165dbe2af6b3acaa763332f5dbdd12f809d59f5bf4304e0642a8005c9" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -661,7 +661,7 @@ dependencies = [ "bevy_macro_utils", "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -736,7 +736,7 @@ checksum = "1b837bf6c51806b10ebfa9edf1844ad80a3a0760d6c5fac4e90761df91a8901a" dependencies = [ "bevy_macro_utils", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -794,7 +794,7 @@ dependencies = [ "bevy_macro_utils", "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -893,7 +893,7 @@ dependencies = [ "bevy_macro_utils", "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -1062,7 +1062,7 @@ dependencies = [ "parking_lot", "proc-macro2", "quote", - "syn", + "syn 2.0.101", "toml_edit", ] @@ -1239,7 +1239,7 @@ dependencies = [ "bevy_macro_utils", "proc-macro2", "quote", - "syn", + "syn 2.0.101", "uuid", ] @@ -1327,7 +1327,7 @@ dependencies = [ "bevy_macro_utils", "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -1424,7 +1424,7 @@ dependencies = [ "bevy_macro_utils", "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -1556,6 +1556,23 @@ dependencies = [ "tracing", ] +[[package]] +name = "bevy_ui_text_input" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5d020c0140e054a114f3bacb31c1a57bc9ae6a544025cbf02cc492b4a426510" +dependencies = [ + "arboard", + "bevy", + "cosmic_undo_2", + "once_cell", + "regex", + "sys-locale", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "bevy_utils" version = "0.16.1" @@ -1638,7 +1655,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn", + "syn 2.0.101", ] [[package]] @@ -1768,7 +1785,7 @@ checksum = "7ecc273b49b3205b83d648f0690daa588925572cc5063745bfe547fe7ec8e1a1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -1874,6 +1891,46 @@ dependencies = [ "libloading", ] +[[package]] +name = "clap" +version = "4.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "clap_lex" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" + [[package]] name = "clipboard-win" version = "5.4.0" @@ -2061,6 +2118,17 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "cosmic_undo_2" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afd123aba915d4643617882bd494db7aaa4be4f4ed84e7b8cee2fd45efe41afe" +dependencies = [ + "derivative", + "rustc_version", + "serde", +] + [[package]] name = "cpal" version = "0.15.3" @@ -2176,6 +2244,17 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "derive_more" version = "1.0.0" @@ -2193,7 +2272,7 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", "unicode-xid", ] @@ -2323,7 +2402,7 @@ checksum = "f97b51c5cc57ef7c5f7a0c57c250251c49ee4c28f819f87ac32f4aceabc36792" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -2563,7 +2642,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -2789,7 +2868,7 @@ dependencies = [ "inflections", "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -3108,7 +3187,7 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -3181,7 +3260,7 @@ checksum = "f3c30758ddd7188629c6713fc45d1188af4f44c90582311d0c8d8c9907f60c48" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -3472,8 +3551,11 @@ dependencies = [ "bevy-inspector-egui", "bevy_skein", "bevy_third_person_camera", + "bevy_ui_text_input", + "clap", "env_logger", "log", + "shlex", ] [[package]] @@ -3733,7 +3815,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -3784,7 +3866,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -4226,7 +4308,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -4332,7 +4414,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.101", ] [[package]] @@ -4369,7 +4451,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30" dependencies = [ "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -4675,6 +4757,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.38.44" @@ -4779,6 +4870,21 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f7d95a54511e0c7be3f51e8867aa8cf35148d7b9445d44de2f943e2b206e749" +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + [[package]] name = "send_wrapper" version = "0.6.0" @@ -4802,7 +4908,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -4979,6 +5085,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "strum" version = "0.26.3" @@ -4998,7 +5110,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn", + "syn 2.0.101", ] [[package]] @@ -5018,6 +5130,17 @@ dependencies = [ "zeno", ] +[[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.101" @@ -5117,7 +5240,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -5128,7 +5251,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -5255,7 +5378,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -5456,7 +5579,7 @@ checksum = "41b6d82be61465f97d42bd1d15bf20f3b0a3a0905018f38f9d6f6962055b0b5c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -5524,7 +5647,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn", + "syn 2.0.101", "wasm-bindgen-shared", ] @@ -5559,7 +5682,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -5967,7 +6090,7 @@ checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -5978,7 +6101,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -5989,7 +6112,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -6000,7 +6123,7 @@ checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -6011,7 +6134,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -6022,7 +6145,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -6518,7 +6641,7 @@ checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index dbd110b..f5d43e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,9 @@ bevy_third_person_camera = "0.3.0" env_logger = "0.11.8" log = "0.4.27" bevy_skein = { git = "https://github.com/rust-adventure/skein.git" } +clap = { version = "4", features = ["derive"] } +shlex = "1.3.0" +bevy_ui_text_input = "0.5.2" # Enable a small amount of optimization in the dev profile. [profile.dev] diff --git a/assets/fonts/DepartureMono-1.500/DepartureMono-Regular.otf b/assets/fonts/DepartureMono-1.500/DepartureMono-Regular.otf new file mode 100644 index 0000000000000000000000000000000000000000..e0b2502131fcb7befea378d6648d380c2591b3a5 GIT binary patch literal 84480 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
*vg8Kdty$3)e9+BS? zP=W1!1yxJ9bwC^hbsk}Nfx;&STtR_aPoPmZWY2>-df*ZgoXSDP)?RR95n2j<*8)p| z(=KS33w)Eo?*pJ2ACNb}WhcZ)n1)Z_3<=s53L4{pSPs$!I??UB7ASLmKL8oyVuLl) zp!-50&H{%Vd>0X@WrZpND)tbjgB$^ldT_fCj$hGn2E2MT;pjR0*^K+9QB6{U_`!SsQ$I4B8#Gc2giK@<@nVNkOPTrfau63|jZ zP_71DRRZc?s7HSPv{xNm=S70lgN^wF+TsX0`~y^7LR5keOTeZMmf}E$K$L>Y2#BFb ze26m8HbqFo25JRF#UB+&wGWzJgE<0bAt-R56bWw8LBaqOYM{2x&j5Cm78$JZ^fzEHIPziP162=c z!>D73g3>D}F2VH(sJ;NH04HG3#apmC0Nn5Zc@5-OkSjse9Ox{KK5&Z=tR7@BHg(AD z4N!uGdKb+!WK|$HAsdOJ1GK;gR5YNS!uA8wzeN=V1vYZG7qV~@F)IlkRtFgd9T!5~ zPz{-71`RNSOCeAhpaM1xynFXR^V zN`sgIwHtfN|4xOp0QLgJE@*f_FOXq}av^dMPl93rI&K3h+dhF?Nl+((hPkvrg)C%T zs}EGUftm1uyIm~sfxBJsfxGXZjs$?%eak>M@FTLwmk4-8)z7#Y4Zd}m-}_{H##fsv7ck&A(mk%y6o zfsv7)k)MH)QIJuHfss*+QH+6+QG!u|fss*?QIdg)QHoKTfss*;QJ#U3QIS!Vfss+2 zQJsO2QH#-tfsxUS(VT&i(TdTEfsxUU(UF0X(S^~AfsrwQF_?joF_bZsfsrwsF@}MW zF^(~wfsrwhF^PeZF_kfufsrwtF^hqbF_*EBfswJ8v4nwn z89y<8VPItZ#`uGQk?|K30|O%yHxoAlBa;x5Fasl#IFlp;Ba<|fGy@}(Dw8S$BaC!um>C!t1Q-|?6c`v7bQqWzm{_t|@);Od3R%h-SQ!`@ zSXe4pDp{&oY8jXqSipK2z$%!)D%e@-SUSLIn~{Nmfr}xCp^AaY#my&#fiEYqB#%Le z!GM8*L5YFY*)c?cLC4q814R1!C@|>w`M4-B=mh!uDlq6UF#P||zy!`G5EcUi69aQ@ zVo^4O2$amu&1R5eU;w)pl%GH>W(F1pRt7c(KE@)(9~^ZY=Qs@*SQr=>7#TP~p~=9* zz{LE7p^kx(!JKhEC}tQ}Gp=D=%ebC#C*vu`%ZzszUx31&@e|``#xIQD7{4?AVEo1S zoAD3hU&eoo|Ctz=xIr<%B+ewkB*`SrqzVdmCVM6irVvm_GsQ8*GbJ!3F(oslFr_i2 zGi5MkGG#GkGvzRtb4D>RfH2222n}K*V~{ul1BVF+!^A;iAU235gpt*8hA=RIu?Lif z@nJMb+yhknKvr1BV+Fqq9MLB+S6TZp6UI;0?+t43dmrkX*sTpvS<(_=~B4fr)_?Esa(( zu(MRNR5J*$)UtFi2r@7+_<&1a25{-i0WN*H!KE(`xb)=(m%c*a(pLmr`ig-|UwLrp zs{k&26~U#i8o2aT2baFu;L_I=T>6@UOJ8$v>1zoteXYRdt~I#awE>sAcHnZ?8C>qV zfXiK9aJlObE_VaKHj8y9wZOHxXR!ri07fOmMlI4K8{>?lyzV-8OK!+YT;wJHX{`FSy+81DCrKz~$~taJf4RT<*>Wm%9tVf$7$yf|gXCZ|hz-IpGeF`XHb@MFLHd~Qp}G|$4pIlgAbA)cL^D4D`y&D* z&io3&2FZccgJ=*2*@4Uli6LW<9LNlq7>JLJ4KfE=9f%K72ckiGU~(Wn2qTMu)WI;i zIEW9EL#ENiL3|Jfse#cTHVA|GVEZc=7+8^U4mezbz+yQJ3?OwN4CaH>KwJ;3QgDeN)S3t}L>xZcW(I7d9+6Yh_fiT#eAaRg9G6sp|ut4ksvC|kB zKyG0Ez`(%#4dPCa8^QYP!2SZqJxC4MUXUKBc_4Lgai|zr4U7$O6T~i%7?NIOeGClD ze;628m|%Lr=2k%L2B`zN1tbQ-EF55Qgqa|7K^Ti&C~*K%1H#B|fbn5sFd8Hd!ca3A z7#W3`)EF2U9)d}4Fj>oZ2qX?78EXG$F)%XJ{s*;!YZ<>Vuz_3epw>L7JZR)p{fsgKXeo&|qL>Fko1Lw{^$Jzyz*U zB^ekPK;cvbr9o_5I1Nn=NDpzCkzqY!90Q01^?cSdPGDeSU;~H5dT_Q?BfZ4~uCLaF2caZzzB+0Mg}lToB`CW1j)03|6h2agO4`Tph2xA0e3}XTo zeIT7!^fKBoIxxC0dLZj&OkpfxY+;YCG1V|NF?BHYF->8b#k7EF8Pgi3O-wtO_Anh_ zI>K~<=?v2arYlT0nC>t=V0yyzg6RzdBSR+xBf~@nCdR3Z)eMY`H4Kc5wG51mbqtJ* z^$d)R4GfHojSP&8O$>~T%?yl;Eewo|tqhEeZ48Wz?F@{J9Sls2(-=D$7#X`57#X`6 z7#VvQ7#Ten7#VvR7#aH*7#RZ@7#aH+7#SxpFfvYLU}T)cz{oh6fst_v13R-2vkcQ$ zrq@h|nXWTEWMF3&W|n38#`K2i2-6LwM-1%DBFu73-A#B`eJB?A-F8L+))nRYTTF`Z-D#lXaLo@qA&6VnBzJq%1t7n$}l zFfmGB7b+V>-mZ$n=_l5fraXZy6Yw-Z3yTy$7dd zMy8Jpj7*;x7@0mZFfx5%U}UypU}XBwz{p&~z{pa?z{pU@z{DWPAi^NUpv+*zV9H?2 z;LH%j5W*0_5YLdrkj+rdP{&Zu(8AEp(91A^VG6@yhUE-v7}hduW7xxRn&BM76^6SE zZy4S&{9yRU@Sl;Fk&jV;QIt`ZQGrpD(UQ@I(U#Gf(T&lE(HERgk{Qz&vl(+3OPO{u z?PA)^w1;Ug(>|vCOb3__G96+%%yfk5DAO^f<4h-*PBNWhI?Z&3=`7PZrt?e}m@YD1 zV!F(9h3P8OHKvD5kC+}aJz;vv^o;2_(+j4TOs|-}GJRwE&h&%nC(|#c-%Nj){xbbz zHet44&SI$q*UwB0JPaz}G6_^}fX3}Wbt?md1Op=jBf~#v2>=oYi9p2}c|m<-Jl5tP0dBfRyeG4ANkKGBNA{ z%YZ^$2C82gtbZrdAuykbA)8SX$xUqkzk%h?GF=19L)_2ApvNFWY{~(}CKK3vriV*i_IXrvDuDkzbpx*1E_2WVT6|q@EFG(vslUw zaLj^J23pL5av>;fiO5XGcjZ{>|xXd0O`OmdH?_b literal 0 HcmV?d00001 diff --git a/assets/fonts/DepartureMono-1.500/LICENSE b/assets/fonts/DepartureMono-1.500/LICENSE new file mode 100644 index 0000000..de52476 --- /dev/null +++ b/assets/fonts/DepartureMono-1.500/LICENSE @@ -0,0 +1,93 @@ +Copyright 2022–2024 Helena Zhang (helenazhang.com). + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/src/debugging.rs b/src/debugging.rs index 3147859..02560fa 100644 --- a/src/debugging.rs +++ b/src/debugging.rs @@ -1,19 +1,14 @@ //! Seperate out debugging uis/plugins in it's own module for cleanliness. use bevy::prelude::*; -use bevy_inspector_egui::{ - bevy_egui::EguiPlugin, - quick::{StateInspectorPlugin, WorldInspectorPlugin}, -}; - -use crate::{AppState, menus}; +use bevy_inspector_egui::bevy_egui::EguiPlugin; pub fn plugin(app: &mut App) { app.add_plugins(EguiPlugin { enable_multipass_for_primary_context: true, }) .add_plugins(( - WorldInspectorPlugin::new(), - StateInspectorPlugin::::new(), - StateInspectorPlugin::::new(), + // WorldInspectorPlugin::new(), + // StateInspectorPlugin::::new(), + // StateInspectorPlugin::::new(), )); } diff --git a/src/game.rs b/src/game.rs index 8120eb6..731d28e 100644 --- a/src/game.rs +++ b/src/game.rs @@ -6,6 +6,7 @@ use crate::{ }; mod camera; +mod debug; mod scene; mod tram { use bevy::prelude::*; @@ -28,6 +29,6 @@ pub fn plugin(app: &mut App) { OnExit(AppState::Ingame), despawn::.in_set(GameplaySet), ) - .add_plugins(camera::plugin); + .add_plugins((debug::plugin, camera::plugin)); app.configure_sets(Update, GameplaySet.run_if(in_state(AppState::Ingame))); } diff --git a/src/game/camera.rs b/src/game/camera.rs index a1c1fa0..fddf235 100644 --- a/src/game/camera.rs +++ b/src/game/camera.rs @@ -3,7 +3,7 @@ use bevy::prelude::*; use bevy_third_person_camera::*; -use crate::{AppState, TPCTarget}; +use crate::AppState; use super::GameplaySet; diff --git a/src/game/debug.rs b/src/game/debug.rs new file mode 100644 index 0000000..6eeee43 --- /dev/null +++ b/src/game/debug.rs @@ -0,0 +1,13 @@ +use bevy::prelude::*; + +mod console; + +#[derive(Event)] +enum DebugEvent { + CloseDebugConsole, + PrintToConsole(String), +} + +pub(super) fn plugin(app: &mut App) { + app.add_plugins(console::plugin).add_event::(); +} diff --git a/src/game/debug/console.rs b/src/game/debug/console.rs new file mode 100644 index 0000000..d00d656 --- /dev/null +++ b/src/game/debug/console.rs @@ -0,0 +1,105 @@ +use bevy::prelude::*; +use ui::{ + DebugConsole, autofocus, autoscroll, handle_open_console, process_prompt, update_content, + update_scroll_position, +}; + +use crate::{AppState, cleanup::despawn, game::GameplaySet}; + +use super::DebugEvent; + +mod cli; +mod ui; + +pub(super) fn plugin(app: &mut App) { + app.init_state::() + .add_systems( + OnEnter(ConsoleState::Open), + (open_console, (autofocus, autoscroll).after(open_console)), + ) + .add_systems( + Update, + ( + handle_open_console + .in_set(GameplaySet) + .run_if(in_state(ConsoleState::Closed).and(in_state(AppState::Ingame))), + update_content.run_if(resource_changed::), + ( + update_scroll_position, + process_prompt, + execute_console_events, + ) + .run_if(in_state(ConsoleState::Open)), + ), + ) + .add_systems(OnExit(ConsoleState::Open), despawn::) + .insert_resource::(ConsoleLog::new()); +} + +/// the usize is a read index +#[derive(Resource, Clone)] +struct ConsoleLog(Vec, usize); + +impl ConsoleLog { + fn new() -> Self { + Self( + vec![ConsoleEvent::Output(String::from( + "Welcome to the dev console :3\n (i should probably put some proper info into this some time)", + ))], + 1, + ) + } + + fn unread(&mut self) -> &[ConsoleEvent] { + let res = &self.0[self.1..]; + self.1 = self.0.len(); + res + } + + pub fn input(&mut self, content: &str) { + self.0.push(ConsoleEvent::Input(content.to_string())) + } + + pub fn output(&mut self, content: &str) { + self.0.push(ConsoleEvent::Output(content.to_string())) + } + + pub fn error(&mut self, content: &str) { + self.0.push(ConsoleEvent::Error(content.to_string())) + } +} + +#[derive(Clone, Debug)] +enum ConsoleEvent { + Input(String), + Output(String), + Error(String), +} + +const OPEN_CONSOLE_DEFAULT: KeyCode = KeyCode::KeyC; + +#[derive(States, Default, Debug, Clone, PartialEq, Eq, Hash, Reflect)] +enum ConsoleState { + #[default] + Closed, + Open, +} + +fn open_console(mut c: Commands, console_data: Res, asset_server: Res) { + log::info!("opening debug console..."); + c.spawn(ui::console(console_data.clone(), asset_server.clone())); +} + +fn execute_console_events( + mut ev_reader: EventReader, + mut log: ResMut, + mut next_state: ResMut>, +) { + for ev in ev_reader.read() { + match ev { + DebugEvent::CloseDebugConsole => next_state.set(ConsoleState::Closed), + DebugEvent::PrintToConsole(s) => log.output(s), + _ => todo!(), + }; + } +} diff --git a/src/game/debug/console/cli.rs b/src/game/debug/console/cli.rs new file mode 100644 index 0000000..4fe321d --- /dev/null +++ b/src/game/debug/console/cli.rs @@ -0,0 +1,32 @@ +use clap::{Parser, Subcommand}; + +use crate::game::debug::DebugEvent; + +pub(super) fn respond(line: &str) -> Result { + let might_be_help_call = line.contains("help") || line.contains("-h"); + let args = shlex::split(line).ok_or("Invalid Quoting")?; + let cli = Cli::try_parse_from(args).map_err(|e| e.to_string()); + + if might_be_help_call && cli.as_ref().is_err_and(|item| item.starts_with("Usage: ")) { + let Err(s) = cli else { unreachable!() }; + return Ok(DebugEvent::PrintToConsole(s)); + } + + Ok(match cli?.cmd { + Commands::Close => DebugEvent::CloseDebugConsole, + Commands::Echo { text } => DebugEvent::PrintToConsole(text), + }) +} + +#[derive(Debug, Parser)] +#[command(multicall = true)] +struct Cli { + #[command(subcommand)] + cmd: Commands, +} + +#[derive(Debug, Subcommand)] +enum Commands { + Close, + Echo { text: String }, +} diff --git a/src/game/debug/console/ui.rs b/src/game/debug/console/ui.rs new file mode 100644 index 0000000..de265e3 --- /dev/null +++ b/src/game/debug/console/ui.rs @@ -0,0 +1,112 @@ +use bevy::{ + input::mouse::{MouseScrollUnit, MouseWheel}, + input_focus::InputFocus, + picking::hover::HoverMap, + prelude::*, +}; + +mod components; + +#[derive(Component)] +pub(super) struct DebugConsole; +#[derive(Component)] +struct TextInput; + +use bevy_ui_text_input::TextSubmitEvent; +pub(super) use components::console; +use components::{Content, Prompt}; + +use crate::game::debug::DebugEvent; + +use super::{ConsoleEvent, ConsoleLog, ConsoleState, OPEN_CONSOLE_DEFAULT, cli::respond}; + +// stolen from scrolling example +pub fn update_scroll_position( + mut mouse_wheel_events: EventReader, + hover_map: Res, + mut scrolled_node_query: Query<&mut ScrollPosition>, +) { + for mouse_wheel_event in mouse_wheel_events.read() { + let (dx, dy) = match mouse_wheel_event.unit { + MouseScrollUnit::Line => (mouse_wheel_event.x * 18., mouse_wheel_event.y * 18.), + MouseScrollUnit::Pixel => (mouse_wheel_event.x, mouse_wheel_event.y), + }; + + for (_pointer, pointer_map) in hover_map.iter() { + for (entity, _hit) in pointer_map.iter() { + if let Ok(mut scroll_position) = scrolled_node_query.get_mut(*entity) { + scroll_position.offset_x -= dx; + scroll_position.offset_y -= dy; + } + } + } + } +} + +pub fn autofocus(mut input_focus: ResMut, prompt: Single>) { + input_focus.set(*prompt); +} + +pub fn autoscroll(mut scrollables: Single<&mut ScrollPosition, With>) { + scrollables.offset_y = f32::MAX; +} + +pub fn handle_open_console( + kb_input: Res>, + mut console_open_state: ResMut>, +) { + if kb_input.pressed(OPEN_CONSOLE_DEFAULT) { + console_open_state.set(ConsoleState::Open); + } +} + +pub fn process_prompt( + mut log: ResMut, + mut submit_reader: EventReader, + mut debug_event_writer: EventWriter, + prompt: Single>, +) { + for submit in submit_reader.read() { + if submit.entity == *prompt && !submit.text.trim().is_empty() { + if submit.text.trim() != "close" { + log.input(&submit.text); + } + match respond(&submit.text) { + Ok(debug_ev) => { + debug_event_writer.write(debug_ev); + } + Err(e) => log.error(&e), + } + } + } +} + +pub fn update_content( + mut c: Commands, + content: Single>, + mut log: ResMut, + asset_server: Res, + mut scrollables: Single<&mut ScrollPosition, With>, +) { + c.entity(*content).with_children(|parent| { + for item in log.unread() { + match dbg!(item) { + ConsoleEvent::Input(s) => { + parent.spawn(components::input(s.to_string(), &asset_server)); + } + ConsoleEvent::Output(s) => { + parent.spawn(components::output(s.to_string(), &asset_server)); + } + ConsoleEvent::Error(s) => { + parent.spawn(components::error( + // dirty hack so they're not called subcommands + s.replace("subcommand", "command").to_string(), + &asset_server, + )); + } + _ => {} + }; + } + }); + scrollables.offset_y = f32::MAX; +} diff --git a/src/game/debug/console/ui/components.rs b/src/game/debug/console/ui/components.rs new file mode 100644 index 0000000..c9e6d0e --- /dev/null +++ b/src/game/debug/console/ui/components.rs @@ -0,0 +1,180 @@ +use bevy::{ecs::spawn::SpawnWith, prelude::*}; +use bevy_ui_text_input::{TextInputMode, TextInputNode, TextInputPrompt}; + +use crate::game::debug::console::{ConsoleEvent, ConsoleLog}; + +use super::DebugConsole; + +#[derive(Component)] +pub struct Prompt; +#[derive(Component)] +pub struct Content; + +pub fn console(data: ConsoleLog, asset_server: AssetServer) -> impl Bundle { + ( + DebugConsole, + Node { + justify_self: JustifySelf::Center, + width: Val::Percent(85.), + max_height: Val::Percent(100. / 3.), + flex_direction: FlexDirection::Column, + border: UiRect { + left: Val::Px(2.), + right: Val::Px(2.), + top: Val::Px(0.), + bottom: Val::Px(2.), + }, + ..default() + }, + BorderColor(Color::BLACK), + ZIndex(i32::MAX), + BackgroundColor(Color::Srgba(Srgba::BLACK.with_alpha(0.2))), + children![ + ( + Content, + Node { + padding: UiRect::all(Val::Px(10.)), + overflow: Overflow::scroll_y(), + flex_direction: FlexDirection::Column, + ..default() + }, + Children::spawn(SpawnWith({ + let asset_server = asset_server.clone(); + move |parent: &mut ChildSpawner| { + for item in data.0 { + match item { + ConsoleEvent::Input(s) => { + parent.spawn(input(s.to_string(), &asset_server)) + } + ConsoleEvent::Output(s) => { + parent.spawn(output(s.to_string(), &asset_server)) + } + ConsoleEvent::Error(s) => { + parent.spawn(error(s.to_string(), &asset_server)) + } + }; + } + } + })) + ), + ( + Node { + width: Val::Percent(100.), + padding: UiRect { + left: Val::Px(10.), + right: Val::Px(10.), + top: Val::Px(10.), + bottom: Val::Px(10.) + }, + ..default() + }, + BackgroundColor(Color::Srgba(Srgba::gray(0.2).with_alpha(0.3))), + children![ + ( + Text::new("> "), + TextFont { + font: asset_server + .load("fonts/DepartureMono-1.500/DepartureMono-Regular.otf"), + font_size: 14., + ..default() + } + ), + ( + Prompt, + Node { + width: Val::Auto, + flex_grow: 1., + ..default() + }, + TextInputNode { + clear_on_submit: true, + mode: TextInputMode::SingleLine, + is_enabled: true, + focus_on_pointer_down: true, + ..default() + }, + TextInputPrompt { + text: String::new(), + ..default() + }, + TextFont { + font: asset_server + .load("fonts/DepartureMono-1.500/DepartureMono-Regular.otf"), + font_size: 14., + ..default() + }, + ) + ] + ), + ], + ) +} + +pub fn input(content: String, asset_server: &AssetServer) -> impl Bundle { + console_output( + Srgba::GREEN.with_alpha(0.05), + UiRect::all(Val::Px(1.)), + Srgba::GREEN, + format!("> {content}"), + asset_server, + ) +} +pub fn output(content: String, asset_server: &AssetServer) -> impl Bundle { + console_output( + Srgba::WHITE.with_alpha(0.05), + UiRect::all(Val::ZERO), + Srgba::NONE, + content, + asset_server, + ) +} +pub fn error(content: String, asset_server: &AssetServer) -> impl Bundle { + console_output( + Srgba::RED.with_alpha(0.025), + UiRect::all(Val::Px(2.)), + Srgba::RED.with_alpha(0.75), + content, + asset_server, + ) +} + +fn console_output( + background_color: Srgba, + border_width: UiRect, + border_color: Srgba, + content: String, + asset_server: &AssetServer, +) -> impl Bundle { + ( + Node { + margin: UiRect { + left: Val::Px(0.), + right: Val::Px(0.), + top: Val::Px(0.), + bottom: Val::Px(10.), + }, + padding: UiRect::all(Val::Px(7.5)), + border: border_width, + ..default() + }, + BorderRadius::all(Val::Px(7.5)), + BorderColor(Color::Srgba(border_color)), + BackgroundColor(Color::Srgba(background_color)), + Pickable { + should_block_lower: false, + ..default() + }, + children![( + Text(content), + TextFont { + font: asset_server.load("fonts/DepartureMono-1.500/DepartureMono-Regular.otf"), + font_size: 14., + ..default() + }, + Pickable { + should_block_lower: false, + ..default() + }, + )], + ) +} diff --git a/src/main.rs b/src/main.rs index e5035a4..6868b28 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ #![feature(iter_collect_into)] use bevy::prelude::*; use bevy_skein::SkeinPlugin; +use bevy_ui_text_input::TextInputPlugin; mod camera; mod cleanup; @@ -21,7 +22,7 @@ fn main() { App::new() .register_type::() .add_systems(Startup, camera::setup) - .add_plugins(DefaultPlugins) + .add_plugins((DefaultPlugins, TextInputPlugin)) .add_plugins((game::plugin, menus::plugin, debugging::plugin)) .add_plugins(SkeinPlugin::default()) .init_state::()