From 299d97cd8232b25c43e509a12c5cc85fcced19fc Mon Sep 17 00:00:00 2001 From: RTTV Date: Sun, 18 Feb 2024 12:26:04 -0500 Subject: [PATCH] did some stuff, added sort --- Cargo.toml | 2 +- build.rs | 18 +-- clipboard.js | 5 +- src/alert.rs | 10 +- src/assets.rs | 36 +++--- src/assets/atlas.hex | Bin 262144 -> 262144 bytes src/assets/atlas.png | Bin 20924 -> 0 bytes src/assets/build/atlas.png | Bin 0 -> 21067 bytes src/assets/{ => build}/icon_128.ico | Bin src/assets/{ => build}/icon_16.ico | Bin src/assets/{ => build}/icon_256.ico | Bin src/assets/{ => build}/icon_32.ico | Bin src/assets/{ => build}/icon_48.ico | Bin src/assets/{ => build}/icon_64.ico | Bin src/assets/{ => build}/unicode.hex | Bin src/assets/{ => discs}/11.hex | Bin src/assets/{ => discs}/5.hex | Bin src/assets/{ => discs}/mellohi.hex | Bin src/assets/{ => discs}/otherside.hex | Bin src/assets/{ => discs}/pigstep.hex | Bin src/assets/{ => discs}/relic.hex | Bin src/assets/{ => discs}/stal.hex | Bin src/assets/{ => discs}/ward.hex | Bin src/decoder.rs | 11 +- src/element_action.rs | 80 +++++++------- src/elements/array.rs | 4 +- src/elements/chunk.rs | 17 +-- src/elements/compound.rs | 9 +- src/elements/element.rs | 31 +++--- src/elements/list.rs | 7 +- src/main.rs | 160 ++++++++++++++++----------- src/selected_text.rs | 2 +- src/tab.rs | 52 ++++----- src/window.rs | 9 +- src/workbench.rs | 74 +++++++++---- 35 files changed, 301 insertions(+), 226 deletions(-) delete mode 100644 src/assets/atlas.png create mode 100644 src/assets/build/atlas.png rename src/assets/{ => build}/icon_128.ico (100%) rename src/assets/{ => build}/icon_16.ico (100%) rename src/assets/{ => build}/icon_256.ico (100%) rename src/assets/{ => build}/icon_32.ico (100%) rename src/assets/{ => build}/icon_48.ico (100%) rename src/assets/{ => build}/icon_64.ico (100%) rename src/assets/{ => build}/unicode.hex (100%) rename src/assets/{ => discs}/11.hex (100%) rename src/assets/{ => discs}/5.hex (100%) rename src/assets/{ => discs}/mellohi.hex (100%) rename src/assets/{ => discs}/otherside.hex (100%) rename src/assets/{ => discs}/pigstep.hex (100%) rename src/assets/{ => discs}/relic.hex (100%) rename src/assets/{ => discs}/stal.hex (100%) rename src/assets/{ => discs}/ward.hex (100%) diff --git a/Cargo.toml b/Cargo.toml index 0ca3c9b..11d93d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nbtworkbench" -version = "1.1.0" +version = "1.2.0" edition = "2021" description = "A modern NBT Editor written in Rust designed for performance and efficiency." license-file = "LICENSE" diff --git a/build.rs b/build.rs index 41f6f8c..64a71a1 100644 --- a/build.rs +++ b/build.rs @@ -6,8 +6,8 @@ use std::fs::write; use std::io::Read; use std::mem::MaybeUninit; -const UNICODE: &[u8] = include_bytes!("src/assets/unicode.hex"); -const ATLAS: &[u8] = include_bytes!(r"src/assets/atlas.png"); +const UNICODE: &[u8] = include_bytes!("src/assets/build/unicode.hex"); +const ATLAS: &[u8] = include_bytes!(r"src/assets/build/atlas.png"); fn main() { { write(r"src\assets\atlas.hex", zune_png::PngDecoder::new(ATLAS).decode_raw().unwrap()).unwrap(); } @@ -37,14 +37,14 @@ fn main() { write(r"src/assets/unicode.hex.zib", &buf).unwrap(); } - if std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("windows") { + if std::env::var("CARGO_CFG_TARGET_OS").unwrap() == "windows" { if let Err(e) = winres::WindowsResource::new() - .set_icon_with_id("src/assets/icon_16.ico", "16") - .set_icon_with_id("src/assets/icon_32.ico", "32") - .set_icon_with_id("src/assets/icon_48.ico", "48") - .set_icon_with_id("src/assets/icon_64.ico", "64") - .set_icon_with_id("src/assets/icon_128.ico", "128") - .set_icon_with_id("src/assets/icon_256.ico", "!") + .set_icon_with_id(r"src/assets/build/icon_256.ico", "1") + .set_icon_with_id(r"src/assets/build/icon_128.ico", "2") + .set_icon_with_id(r"src/assets/build/icon_64.ico", "3") + .set_icon_with_id(r"src/assets/build/icon_48.ico", "4") + .set_icon_with_id(r"src/assets/build/icon_32.ico", "5") + .set_icon_with_id(r"src/assets/build/icon_16.ico", "6") .compile() { eprintln!("Error! {e}"); diff --git a/clipboard.js b/clipboard.js index 1bdcacd..bcfd33c 100644 --- a/clipboard.js +++ b/clipboard.js @@ -29,9 +29,8 @@ export function getClipboard() { } export function onInput() { - // is not firefox - if (typeof InstallTrigger === 'undefined') { - window.navigator.clipboard.readText().then((str) => clipboard = str) + if (!navigator.userAgent.toLowerCase().includes("firefox")) { + window.navigator.clipboard.readText().then((str) => clipboard = str).catch(x => x) } } diff --git a/src/alert.rs b/src/alert.rs index 8c6ef26..02b63d0 100644 --- a/src/alert.rs +++ b/src/alert.rs @@ -2,7 +2,7 @@ use crate::assets::{ALERT_TEXT_Z, ALERT_UV, ALERT_Z, HEADER_SIZE}; use crate::color::TextColor; use crate::vertex_buffer_builder::{Vec2u, VertexBufferBuilder}; use crate::{since_epoch, smoothstep64, StrExt}; -use std::time::{Duration, Instant}; +use std::time::Duration; pub struct Alert { timestamp: Option, @@ -71,15 +71,15 @@ impl Alert { } pub fn is_invisible(&mut self) -> bool { - let ms = (since_epoch() - *self.timestamp.get_or_insert(since_epoch())).as_millis() as usize; - let display_time = (self.message.len() + self.title.len()) * 200 / 3 + 5000; + let ms = since_epoch().saturating_sub(*self.timestamp.get_or_insert(since_epoch())).as_millis() as usize; + let display_time = (self.message.len() + self.title.len()) * 60 + 3000; ms > 500 + display_time } fn get_inset(&mut self) -> usize { - let mut ms = (since_epoch() - *self.timestamp.get_or_insert(since_epoch())).as_millis() as usize; + let mut ms = since_epoch().saturating_sub(*self.timestamp.get_or_insert(since_epoch())).as_millis() as usize; let width = self.width + 24; - let display_time = (self.message.len() + self.title.len()) * 200 / 3 + 5000; + let display_time = (self.message.len() + self.title.len()) * 60 + 3000; if ms < 250 { return (smoothstep64((250 - ms) as f64 / 250.0) * width as f64) as usize } ms -= 250; if ms < display_time { diff --git a/src/assets.rs b/src/assets.rs index a363935..ba5c3b2 100644 --- a/src/assets.rs +++ b/src/assets.rs @@ -1,6 +1,5 @@ use std::mem::ManuallyDrop; -use crate::{since_epoch, log}; -use crate::color::TextColor; +use crate::since_epoch; use crate::vertex_buffer_builder::Vec2u; @@ -14,14 +13,14 @@ pub const UNICODE_LEN: usize = 1_818_624; pub const ICON_WIDTH: usize = 64; pub const ICON_HEIGHT: usize = 64; -const OTHERSIDE_MUSIC_DISC_ICON: &[u8] = include_bytes!("assets/otherside.hex"); -const PIGSTEP_MUSIC_DISC_ICON: &[u8] = include_bytes!("assets/pigstep.hex"); -const MELLOHI_MUSIC_DISC_ICON: &[u8] = include_bytes!("assets/mellohi.hex"); -const FIVE_MUSIC_DISC_ICON: &[u8] = include_bytes!("assets/5.hex"); -const WARD_MUSIC_DISC_ICON: &[u8] = include_bytes!("assets/ward.hex"); -const ELEVEN_MUSIC_DISC_ICON: &[u8] = include_bytes!("assets/11.hex"); -const RELIC_MUSIC_DISC_ICON: &[u8] = include_bytes!("assets/relic.hex"); -const STAL_MUSIC_DISC_ICON: &[u8] = include_bytes!("assets/stal.hex"); +const OTHERSIDE_MUSIC_DISC_ICON: &[u8] = include_bytes!("assets/discs/otherside.hex"); +const PIGSTEP_MUSIC_DISC_ICON: &[u8] = include_bytes!("assets/discs/pigstep.hex"); +const MELLOHI_MUSIC_DISC_ICON: &[u8] = include_bytes!("assets/discs/mellohi.hex"); +const FIVE_MUSIC_DISC_ICON: &[u8] = include_bytes!("assets/discs/5.hex"); +const WARD_MUSIC_DISC_ICON: &[u8] = include_bytes!("assets/discs/ward.hex"); +const ELEVEN_MUSIC_DISC_ICON: &[u8] = include_bytes!("assets/discs/11.hex"); +const RELIC_MUSIC_DISC_ICON: &[u8] = include_bytes!("assets/discs/relic.hex"); +const STAL_MUSIC_DISC_ICON: &[u8] = include_bytes!("assets/discs/stal.hex"); pub const CONNECTION_UV: Vec2u = Vec2u::new(64, 64); pub const UNKNOWN_NBT_UV: Vec2u = Vec2u::new(112, 32); @@ -60,10 +59,10 @@ pub const ZLIB_FILE_TYPE_UV: Vec2u = Vec2u::new(64, 80); pub const SNBT_FILE_TYPE_UV: Vec2u = Vec2u::new(80, 80); pub const MCA_FILE_TYPE_UV: Vec2u = Vec2u::new(96, 80); pub const OPEN_FOLDER_UV: Vec2u = Vec2u::new(112, 80); -pub const UNSELECTED_TOGGLE_OFF_UV: Vec2u = Vec2u::new(0, 64); -pub const UNSELECTED_TOGGLE_ON_UV: Vec2u = Vec2u::new(8, 64); -pub const SELECTED_TOGGLE_OFF_UV: Vec2u = Vec2u::new(0, 72); -pub const SELECTED_TOGGLE_ON_UV: Vec2u = Vec2u::new(8, 72); +pub const UNSELECTED_TOGGLE_ON_UV: Vec2u = Vec2u::new(0, 64); +pub const UNSELECTED_TOGGLE_OFF_UV: Vec2u = Vec2u::new(8, 64); +pub const SELECTED_TOGGLE_ON_UV: Vec2u = Vec2u::new(0, 72); +pub const SELECTED_TOGGLE_OFF_UV: Vec2u = Vec2u::new(8, 72); pub const UNHELD_SCROLLBAR_UV: Vec2u = Vec2u::new(48, 64); pub const HELD_SCROLLBAR_UV: Vec2u = Vec2u::new(54, 64); pub const REMOVE_UV: Vec2u = Vec2u::new(0, 96); @@ -94,13 +93,17 @@ pub const HOVERED_STRIPE_UV: Vec2u = Vec2u::new(112, 128); pub const INVALID_STRIPE_UV: Vec2u = Vec2u::new(112, 112); pub const COPY_RAW_UV: Vec2u = Vec2u::new(3, 131); pub const COPY_FORMATTED_UV: Vec2u = Vec2u::new(19, 131); +#[cfg(not(target_arch = "wasm32"))] pub const OPEN_ARRAY_IN_HEX_UV: Vec2u = Vec2u::new(35, 131); +#[cfg(not(target_arch = "wasm32"))] pub const OPEN_IN_TXT: Vec2u = Vec2u::new(51, 131); pub const SORT_COMPOUND_BY_NAME: Vec2u = Vec2u::new(67, 131); pub const SORT_COMPOUND_BY_TYPE: Vec2u = Vec2u::new(83, 131); +pub const SORT_COMPOUND_BY_NOTHING: Vec2u = Vec2u::new(0, 160); pub const FREEHAND_MODE_UV: Vec2u = Vec2u::new(0, 144); pub const ENABLED_FREEHAND_MODE_UV: Vec2u = Vec2u::new(16, 144); -pub const STEAL_ANIMATION_OVERLAY: Vec2u = Vec2u::new(64, 144); +pub const STEAL_ANIMATION_OVERLAY_UV: Vec2u = Vec2u::new(64, 144); +pub const STAMP_BACKDROP_UV: Vec2u = Vec2u::new(16, 160); pub const BYTE_UV: Vec2u = Vec2u::new(0, 0); pub const SHORT_UV: Vec2u = Vec2u::new(16, 0); @@ -130,7 +133,6 @@ pub const LIST_GHOST_UV: Vec2u = Vec2u::new(32, 48); pub const COMPOUND_GHOST_UV: Vec2u = Vec2u::new(48, 48); pub const INT_ARRAY_GHOST_UV: Vec2u = Vec2u::new(112, 16); pub const LONG_ARRAY_GHOST_UV: Vec2u = Vec2u::new(0, 48); -pub const REGION_GHOST_UV: Vec2u = Vec2u::new(96, 48); pub const CHUNK_GHOST_UV: Vec2u = Vec2u::new(64, 48); pub const ALERT_UV: Vec2u = Vec2u::new(112, 144); @@ -204,7 +206,7 @@ pub fn icon() -> Vec { } let mut scaled = ManuallyDrop::new(core::hint::black_box(scaled)); #[cfg(all(debug_assertions, not(target_arch = "wasm32")))] - log!( + crate::log!( "took {} cycles", unsafe { core::arch::x86_64::_rdtsc() } - start ); diff --git a/src/assets/atlas.hex b/src/assets/atlas.hex index c118463968f09dbcdbb525a14245a54f58547431..38480bf42b983e90ca34ef20a6f9cf4e44c918b9 100644 GIT binary patch delta 1966 zcma)6J!lj`6wb^fc)<%U#NbIHA(&vqXfQ$8BQdkYA3_Xxa265*9wdceK#MB`Emk7f z2qFWw*aft)(vZL$Sa<=26-;3vXdz-@A!uP?VG-8%hJDN2X3gbbdEdPEz3+W*b~)d7 zeBXKLZw9Ml%u^#1`Stgk8$vZ!X4b>E^%%S(utnelfsX_}6ZldWfw#n<|678-C+J6l zekJJlD-oPqVi(rKKMDF9qm^)5huh4ZmS-j>qjI?%VVs@2lwitH-mXnePa8gPwDkC< zFe?51{cTwfDrMy<2g+KWM-`kwSv$raeLI&{1&)?8sA5-a42&j)ELGS{%Q<_ac<-i8 z+ecazCmJs$RH2))S+*xqvUB9is9Yg0wgNGz8zU;fV|-$r_>j}`DypP!ml&jy-qfx` zP0Iya#db^zI2V|^-IbOVRKad!lVeiAXwUu66fKvs?ghKk9lK1?@;a%c<~R951>2K7 z1*U1aOdHvibVtTHnF2K}Z;=X|k_!1sZ-sf}*{bFpRcRx;LTpgk@ov&Qv(ar*iN6W+ z&w%sjdqzK4!yICac%~ejwY&=|fxpb!2kWI$G(J8aQM57U+Tf=uqBnSSik;Q?pUHIN z9{2b$WV)_vIx*T0cH$X0>YC`=*-VX6N+W>L#<2i4tI>&0H8P)94c;D97#q}$c}^~Z z{9%4VEs6rLI%-p_T@;H&by*D5`q=&sG~d=tKhjKBo;Zl>VLK5Y!*)S_V6ViX*2E`9 z@%UbeC;2e=D&Pyssf}8L6mEII)Ko#9(?} zL=SmL?IJFdyj?>4mnGR1Q)3C-!-|1>NQ$k4kO#5#q$K!lwG#bJVc3ZQPEhO0uB9D%a zd=-nuKq=nP9_6>%>zr&c$*yg0Z$qh6g8BJ*UlE2Jzu0x%fp5Uf&CN|XI5>d0xjA1E zMw}1=4i67uYHG??gc0XJ30-oz92AR1p947(MqDD1a8o8GCO|DpA6^4T_I*B64Zj0RXj4V2*T*S$lI%mneF*kZ%yviGZ7|JunS{I{vwqLzH!dJnu44#o=X?p zRT*-mCG#q~%9C`et9zDV`HNJB^yj&HxJ6wq*JV_?P_8U5&;9SJ7|ou$tMzok7(*A{ z!*rqVXe%`WUTUT$%pDE>bG8+|I?pIL&HPuskw0VguQXRY`bo3rtiQ*Hy*P0{|d|{QCe!Ftwin09sma6l8ULO^U*l2A{Nngbj)@^FEvs0@p2uRzt3T=39r*+&+FZ8Ry~2cqqzvbvjtY&_ zM69@&{IALzoC3CoQ31QP(WC5D$pCe*b2%afXS%_>4E&5>yyo}xeGH#-0j_7|;PE>~6pddg$L|%28g3m0EQ$-q#)Liu3iSc6l{eiGvDmHlA5*;;#@9{XcmP_BlM#X7u zXy@qh>AiY6>tke5G*j%AbX>ALKvLwLPgI}Rcn5QFs1UFA(WTAN2TiPLGn z`E)pf%$ErdRtNR07nqP${i$*2x;O=h+ z$_#53MluDcm&A9|v=t0UPg+i;FtpY)f=2?4!ed0xf?L5(5v~|przUIkGO_rqs6Vm6 zqL(1@cdSDTEe7(}!pTG_J7kL>G#`T$S=CBC8@=X(l|-rnU)2yBvFSla&|)N#(!qph z-%agIy+HL+hQB28$|{)ig7(urf4T+zT?8hLOp$$~{Cb8`2}|aMzQGD1p!l*GDSbV?63sQgD{1(Ate71vzypP?`b$|TO|44majnhCFJP3J|ip)Kt z^gsOac`et<>+h6eZ9|2B2*oVPK!MEVnwg>(2LEq4(qlkirF!F!1wv8v!boV@9D+{# ze8ZMsPy)JUF6uqwR;6S&d3mTeT4hVnT7)Z`y#Q6{KX_fjs@<(!5yIGCA}IO3P$`Tg z9kD%7oJwe4HTMyqq*Ad)6*R^2a*pP$A~ORUaa`;PMwcj{;;#&yuNwANr=u7#k?C}3 zZVMegX5iNa*eF0d=NJoQ_H& zP=`R1swkv2uukm-sR&C_1`Hi-y-Bco0XNbZ5l4`k#X@A`d5dfM(;h&8?8S;I1jc+B zI9rcnVk3SHzxnx7EYuPkM8+j7@BFHMP9aF;Pe}Z)wJ_$`4icG`tbV*WcQj_ue$?8T zSwE@8&aHPBY3JJ=nERkrhDbV&iP=+5?jhXpQ1Kx%@S#)p28OkR@yhB073~Y*)Guv< z6dk+5PkCxEslQ$bREU-M&1#L7COVjU>2P@i?iTl#cCeyyL3rR(R($mippB*;YXwu( z?jF_6Ct`#ouknj_n5a6o4B^cDBZie}yZ?>w$T-UeI!HiR-zGy{(b}~lw^q5bv2!$F zak>Ied_~n7&t9sP)~$@W*h{eEvh>30L}VzsnyV4UVtl$zwkt}JB~GYN}7YU z-yOm-X)j|>fDDWFxiw3H(E~RyKKbm}^iYkMC$h3U=| zXzx+(hAc9d)-mU6UlO}WEl$MSj465N zAs%h<&#@FVC@I~t1gRR=g8*H!7;pykhBxvX!8brA-u)YLm^wN<5~{RZ=cmqRyU$5_}x=|-KO7Mi}V2JW%zBdDCYs%h!SPT(tNQKQaC zjo8w&QH_eateFbVhR=p|d>L)-7B_sb%laxM027#@p;ig-eZ%CqR~&5uXUU^#Mf)4$|;7P z$MVyev0Q&O*Eb16ZGA1*@FOapK3!z_1=wRYWa~S;@qmcNY*7~(t5v#HBJJi%rjHO! z8i^?lE{ckS&Y5qoj`!+99tC`w4>F#2;qb)Y#=QE;_4_tVK_i?3lDn_VnT-y3o57=& zs-^Lv!Fg3pNitI^$ln}MZqkA^m;2iOy*c6$@qj&-`^rMj98p@{%vt$ZYd?*4m>Ycm zH}nc}x0ls%FX?}YQ)Z^e&P&J?zpwi_ku`o_FuZ!Gxx;GK$NddFiheH$0#AQwq_7Xg zP=P1V256w`RhYh%lV+3Ia#@}Wc-rO*v-}UmkX8K*T=iah<;EQUwMswUZui!Yo$n4S z{`cFLM)l;&VqAJ<#1?+8Xf}($9xi_hga|2dn`*BI;Mt@9)j>@1Ei^YI9R1^L358Mp zr_3TU5IPf#(XZfP-uZCu4;kS`?XnZS+I!$F%ezIZid;|TF{M&1{(ld zX6tleGHBQQWTmMKXzaK9^(e<@4fcO~SBw*3Py8_wPESwY`-`yw*l(nL?P}(@Txk(@ zi*J|XMfq^O>@qy8!gM>Ls`(0tQCBpV-~$o+HJkpis8t+PJPf)Y)F>z_szJTKH{qz^ zO4T~8RqHg0fAyX^|;G~^3KWGrq zi&+S8Fme}0Gko?JXBRNR_2oI55~g(wNDFAlVy+{Tj`JJ=CzdcICiV>umU5ggH<=*^ z+7IBlqY>ZE{V`fJ@mZzNWwamQddZgeBn3m4w15y(_E?MIog`K!Jr(N7^JtklGlc|3X{m&F&s>wE_=9COmK$6$j4XjK30?>*G7LS74x4IolAlX`gEuIU-ethq>Cno@LsB-KazPO|M{0Gvi5D zSo{=i#>Df+9%{nh4luStb4OOq-36F+EPk`RA^*)K`|aA-w0JpYwtmJceerW;!;4a< zL3xUSqbG_R-LUfZdk2y(TuljXVo65tp+7xO1#IKvdy_I$wtL58!fhto$vc^$JgtdD)PE;!n~NuDUr+PoAd2WM4IBrp8Te-IqKva(VP zhRqqdP@u}FSMu5mX=(!)^4(=NK^V7qA6s@u5mEiu6{q0$-vdqKgu`8U2Rjt?#3QVL zlO`xB4KRLq$v~m|Kg9^5^TNw`o%=r;qpe0c7iffNT-r71yvzl;xVDaB<>? ze1zm#2Ri?AsV&h^uP1o}z@MhEL*`+H9?-nA$QnB9+u%e|LgVhza>ef%s|D1*Ue}lWU{%1(<5-#&hw-lPC z_|L>b5lT$=kCVgKr|au{T`ou!E>6xbMvr;%>#)OquZvM(ynx-W`qr^NqCeethmJt# zgv4&q;Qy^SI{*D2M;l>8=|=HEk9SbiL~HN-i0_oSO$0FcS1W+_2T6BBkPo?)3g2Ga ze;M9VG;58ciUxCYH+GYv-Lwz^{A3~h9rRy_Mau7npJAi8-XYIWthz+Q%l@v@^xgB= zZNMeN3{e{F0N6d|gc!1zl)P7_}b-J22GlT>q zjIY*%AC7BZsI{7Lm()1Sa&nh7ADn7jGOj#(a#d>)3@5{CYf#$}LHHvT61613)D%0= zjxij7Bv&|fU#q{vF4*;?FxSH!je%)*=f;vU`z0Z6Aku{uCJ z!Rmf|wh;78G)LTr@+9?!<>4~mJRq!Uy>v9jE#w5>^JZR9e4-}Uj~WQ_O%$A4kp^#H zicrE#GOie;^n%tEJFHQwK@xE}jjy!KznnUo`sfrTXxK{@%~&CNQ=O-um3m9`$7FGO zhJR3o8`d9|!CJzemPu5H^0yDstcfbOzMd9pceaJ`iuD2WjRW`twkDB=q!efax2MYgRFEZF z9}d{{ic*}QO;jlU{w^+pC)iS74;XNjl*1C}JSc5Su=$Pz3z+g1P$U^0T~>f$LZF!_ zdRvz9-;J=2(e77vY>>qsJ+A_hU<&1HjF-6kcP_0krSvcF;RR*|o4AU>g-Mj2By(kk z(>9zAhSHnzXAX1|_3ups8C%&%@Zu~8b`cY1u5!0+6VJFu2`f$vj%h-rfxv~7%IYJ0 z9JSlZ@X2tqpY=oNJlKFxm!-~yiw0#Pft1MBwp|Pz7@5k2fMJg;6*Z52Ng=e<{Hcn( ztnLvx+W^PeWYnvlCN17u2Qq*BuPkYXezY=P2BSxt2tsbC!-4r|)fi07X&iQW^muid z#7k~z#edEthk+=V@KIF1e|viOT6KRwK9i}ZLR!S#BR4rWYt%v5d9ON*P-?`+U+SP( zW+6BcF%B=V3-*+`6I-F81I)l-5DKt7*Clk-Ra?D$AR&ZSm6^XP`HU ze7(~ve@fL(tQVUsa$PFW@fw6(;`j5hcqal~=ofjHP zw*3OJet++Z(P7V9#(E}4AsHO4&=>O{Kdh~%IaNQz$&(c2C*|+g`g;44R=TRFEjWdX zk#}9B(VJqN1FeG!6L#oEQQ;ZQ7za8KlX)5?a|qz-H*sN{{Mq)_KspH7*0@=ylt9CB zG+9$LV!>Va{(1f1d9#J8`8InCf;^2}DHanS4A99*zNs07bgMJL_ zv6%mdfbFax1*+^bOthxIOsSGdg#~e-Wp7)twpY{&Nv0WcfH6g|riEOZ&vnwxv%S+zvcUE~G$_UX$Y7eJEbWhA&0tpBdGjRJcic3Pr1uAO z0V%qL9lYx~1D+YSu9_H-hVWJ!e4MeG^oKU4L@}}38|@O9VS_M>Qp>+trVj3W%k#l@ z*9P(Gy?F4asECmKaCX6O*b^GAIQMnDZdOx}G(YwLgFXDWCEKMvU`ZR3fvcdgW4rDM z?T+ReC0QT)&AIKA6Ni&uoLinPcHmiaOEk`vKj|D6`7w@b>~e$RhUz2Nq!exXr@S*f zF-0#$1!0m&!?Zluu068x7STj|YDZ4LQ7ibzyH=Uo^OnTr>8OZs`aVs)h#tZjQ^tME z8+V+KHN};h#?JU;KU>2BxoUEnI&Cf~ZD}vOl7>I4kol1Y)|keH+eu%?#4E~Vw%zEn z2W`ZwmKH^^vZyZUDR%AxTq{KXp%dV|^+Y*i{dX~Iez{185Ob=r{c}SM?x8_XT#5J} zgd1&PSA4ly-2l2!F#zN#s+T$bk&&W_e4_2SEy9k~1YZt0B4jE@G$Mx+e z8O#KJmrLiZk}GdG_HMqr=TtDZs`L%<{^HPK^%vIC2H>omN&z5@`O#HDXafghEapFb z(9oD2NcHjM4Jkx`vm_HAL3vsLg$fDdDbe8C7&DHoHuU4=1|obd!rhp(3)A{1LOkZC zkrG)q3zz{<7G^Wdj`<6ilZvw)BEB!@;}6}FT=xs*ur}cJuULFrb_#7Z5xK^WPOXd! zu_*r79inL~h2K*#eykgnm!FQXzZCVh-Q#KMHOSF9ZE{nxD2eCOE7f!IK9D!_3l44` zrP9ZCrI3kbQ25&4K7CPISvgJ$a7FXtg5I5&^`Fk}9&io0QRKboMbyS1?*}gl6`;q0s(Fhtc<%Lco1cvn8nR`SQvT#@R)mV z^Y+Z_!}i*Bl;259nJ7S$lr4|Z>P7VK#TE^%J^;wrGZ=Bu>qoc(+HZhIjzUgLc7SJSROdk9k!G&KGZc;vy4}?UKz9^AaHlHL%l>95HEnAio{^|a#Bj?vn|%Nh z_kRUGH%n6v?_nfjKZRoK|L(=n&961MZ40Fy*UA>KOT;PC$YrQIRft*))<-9$cX%)% z@Z}sULc>mY_Zf`75wTuez(TwWc!hE0PeCyS-z$8+i((EPT$Pp8r}!EvdtP|2sLyS* zLV4s?JoCTD>dk529v}dohROAU%AnNMEA4HA$n)S^N$~2a+mS^lS?b{aF6(ZBZ;lKx zOe{1u_8)YKdmjeJES51#_V-RlkE=4F;LHAAwq_p(ty_9MgqGUxJzl@`Jh$Ng^3kuVBjd! zIW5;Wy&Kn`3G5X(L_%MGl72Xsf~+l{bVt3=U0FH4Wm=&v6B3>|mSTwaf-r8>V! zAGVXE4c6O(w4{&Y1;&W~HnRd`$I*QeSMyaae6>pVX&l-Hr)zCikslpV%GwecsLDjw z;jQ@`2I0TR0qqGhj1>8}X8?*b)W`sEQEN^%=}WtbBCy z;kabRk*~ed^mKHdy|Y`Wv`D-SH7$SLL-dhGlV%GW_9z5+(Esx+&DO|<9)(F{N9V)5 z8my^mvu98sWDv8{mgcTJ=29*4FOTq>W^!(--@2j;i8~9$W^ir$N$*3pwObxo{x&3RqU|WSOFNWU zQZ8BtdtHF7)}7fu+|=n#O_6hl9MBc{FVuKOCf%ckb`v`-wE0!qnKu?7_I!Nq^3?jqS|4nr6v|5`r_TJDs;^7~PC5c?)vK7!wu@gPaQ1PV{ zig8Nozydmp0K}=U2*(OZseL})K0KMeAgyJeVOYl#F;qXx(P6z{SFg1F9_S5ZDnRl;xuG+L{uOrjStTqkIu1#i*rv32SYmOj)X zPa(*)iyE-sUgqixweM+^wKyuL@O~Ea*%DnV2J~wps62}5(Ezf6ol%pxIkX2fpd|*z ztFJ>%N?S>a-?RWR(v+t8pjdu1obooJPN$Ch6$X2LnurgSEIpO!d0->uV?I9P>#PG; zI%R-c0B&waJ9DvbE;gVF>u9~V(QR7-5_MSKGWMGY8}wW4=_|BzcK~OM7&=6;W6BlF zqL;{06s3Hy`+(1QpGlbgkh_|gN{s-f7$<^{!$$G&i^UvU?*mgs_N&+-)1Rc6>aW#AfZq<+Qr1}AMzNv>$7>t+V7_!vRyzVx8jCGY`0>0NMW2D-{l z3l_yJFuD{#jFjcz=pv?iQH{#)Eyp`Cao3gia?X0K%elU5Q9Y`)9hIXVIxr_pSTBvL z^4(ho7I<8=ty8nb#$%xYpS#Nqif#2}ewoR=T`X(tC9=-Z%$+2nL#HlMa0;Dr<^`7m ztBQ;D(Zgez;Tewel-F`~4qe$Kz^ERg>L=BURaS&oh3A4<17?cx9i_WGg>bP7?2=~+-2y&iHEby z_%>c=SwY;%5qyXn1nzrIRzTPf30AreLY`}2i+9y>6FPDFcg{Hg&d5y${tD9;F>>kQ zrn2kOGN@pPBuGh5j~!hHd!t#RW7?tXX`a~gj(ETqfB*rGk_afLwI+;Ah8^F2=#B_G zIm5I=Z>?dJ1cD+Ybu^pG0953w-`nr%+B6cSpQwRpWU3c-&4ah8>rC$}E#qdVcx}h5 z&6vVR!D?K^me1*7j1YLHTELC65MniVQNwJ^^IUfJfW$7Px0k*TFbjO0+fN{9f&Wbq zv;)wmx@|Ri8Y8R)+;&;|^_I%wxl`#+CetO3Z7uUog|^rEVep5OmT~_psby!cl6Uxn z)wxew?B6L^!;{Rnv3X%2#x7T=&X-}(?aH)r62q|{Izc;C_Nywn3Pe=zsYv(daRFG3 z;Ssdyt&M76tH}8a*o~SelCxUA!v#qCD5WF8)6&#&!;}9bF*i};)5nc(Hm`MzM1-2* zdUL!Ny(Opc&XQs9*Mag6x`CQvvUsJtH9rqh*2JH;@V_1XeFRTg^Bt%+^0YiUhEhet z4UB`7#jg+P`kbHAy8%$byw1(sFG))eKeW}-F zASMKKor;IFSFWubcIu#_{n(A z4DV{Zt@-LFL^7-)4wh zi!o{An!ip;G>-J2+0i)k;o-(bX29g+nt$5a9i}DzQT!lb4ou!A%_9jqw@9EC=t~b_ z(r~@e)|9e?Jj#muoO~K4n~PX?fMoWHiV8FU|?w?BbE4n43ntM5j}a>8u5`5FOX(xE?E-~YTA3Ttx*yM|&g*eUuD z?*JxPUv#}X0g(RT+8~sXNZv+8hskoE%}G99u0VL(Z@aDrr3^3M6a=fr-z%ovQ{}j( zls;~0P)6VDIh;}1QYp2c)l=1ZP{ez|$0aWCTTliUlw|fNXEf_z+_2GZ$5+4VkBnYF3Zy*Qkc)a!m)FqV+W10qfb*rjbWKlVrb9{Zj@Z(Psv7gHFl}~dK`kDA+ZS89} zB6J3Xr0a&v_;%5~9e6%TQ0m}S=7H_iy3xXoSu50#aZ4Eod!kHM5c;O?fGi<~`U1)0 z#I}(gQF0b?w@XFMMU5gy8C6q07=tH(&nRZ+p{24%Ssm5UiugPoH}O5WHdzzHimF|5fAiiIjc4-#H{IWbqRr<< zfL>Bf zwNoe)3N`(=&4eR?MCSLVmu2aOng~ki**GoLcG8CAKTZToidO|5x|7t4))GAUZuU>I zJ)!zQY?R?bx%tB>(Hu`2e99@|b)YlZEx)tTiOpzz+Im44a5byHSd<&gIpqrXEo2_D zv9lAu${2l|3-<46J1VE;-S&X9q-SM~kBM!+{ujKF&0Tl=mYWqfpR5>;+*5wLPhWnT=4j!O@)o4QZRy5j??dD_^QNofir1VTb909!|JmoWDK- z*gR303q_hxpN&a4W1N}qj~AwBv1^`#U!ETwRzSSH31#Oie$9?UuXf>#8A!d;ohzc< ztI~^mql`)^wrjTo`H4E(88?C|yy}ZMDAL$KT>sSk>H|Yx38On;x!Iabyh;PR=#eo5 zZjr@k?&GA<6a}70sS6X6J{VrhuSY! z6C-Lo#k^ZwRo2giK$$$xZ(NlL;=yt*l0g});8VDO>JLMvdE$Lm`i`LdaA-67tf6K=7R`m0ME?3N%i}-!Nu2eWFgvY!@e*dnyBNR-j zkwq@7&J;WpkIrNwe*hFZ^uxPH>HZ}n>#JZ*DNdc8xj1A7IDJ|0}y{uclo7>^sd zvTr9!P9MkAQ1X=Cf0aTN_HC=*n|rfwNI#~bygB6R8^DQ{jrd(A4RD_Yn>ZwarZU4BettS-86PMG3_-aAt&+?X z|CeUy)bb0mEx1dt^;FJdp_4t)YubIw4m;epw}T8#F{&qN({Z?>o4+? zQbK!5vY{X6ia{(*9|C0DgF>yV1Ol>8`3qts${sx?_pG7+!a&-YZRY61L+(W}TA8p zpO6lp|dU)&#ZfT?d}BwTJ`dfl;I5et^$6;Cqp>;1|j|6Pn9l;?5R9&B0* z4ifjMbH&IM_50LYV)6-gy1WRt$uj)&U3Y=^tuS`XI&)d23^%ZU2pcx1b-|$_Eflm& ztQHeOX#61qNbnd4kL;Jk%a#%*H|v}6bDHV>IR?SfNQ6FkT|HW0MtHVKY`qRt&N985 zQGatt<{&jOjJ{ZSpMwF;qd@SRORyGwltGiemB8MW3hbA|avc|dHE&AX2^y!|94_IhW}e7*)()bMI{y% zogaVQbX}jB=RwKR3e)qOgYO)SBc?+2vm_4SIkz`d#mVP9X9idKg#bLg*3H`DzR>n# zMNNd_aKGPNu24WNYTKaJvh)az6Y`Z~fn&gZS%2X27cD9-w)o0H>o07>w(6d{8oOKl z8MVO(+el^14q|1Ra|LE~S#{a zgt_IJWTej7c}OW#R@miXe6@2crS$W>&N%u$zDp(ALYm8 zIgFn5F}1nMOKd_Ag>e2s=3h#J-XDv=+`s(KwtFr-E*6SnE8oCz+%%ZaIS7{5)oDK> zPqW02LVU;DZGy09`OdqFgUHMRgQ5^iwEBybynI`ecuwC&I8%LbV|xnl)D-QL z2GbX-+dtR+DsqP-+MMNg&KH&lQUV@b=wHv>XN!lG=$5`J)0q)(kub-w+nUvv6}-0ZY^XajFIHQeZIES_wL zu+U%|pZr8ijcd}aUM8LLPT7#y7A zl}H+`r;^crxuHmW8j#ZGghwS%`GneW?v^k~erYcz`>xD?r002V?3zZ_3<|}b3#=7)oH&I+K(x-K3f6Vk` z%NRkC-JiRHa~fufYZcztHZF#z(sPSiZ|+M5XhW0d@=rzFrhalCMB}TMrt2F*c#=fI z6)(VK@+)0OtF17dwZmTuTM|xuJ{asZmV>m$mGfd(+Oa(rINx(U80X)l;Mja&``y~` zoRdP=k}IQi*ol;D;p?CtPf!M;-Iw(3%lH*E{3myip*qKEmJ=M$IT!ABJ}hN^85DWW z!j}&Y3YZpawM?#J?v|=M+y3!gkh98|Vb5l_#^1}aQofOOV0_5RZ?UONuWpE2x+s}| z8V2bYuZMtM4OITYQ>IGKJ!3E5nvKl(ZG)?-KYV+fb$(?vFP+6K_d7TG?Vgki)W$A4 zlD1YX7n$27#QpFNpTAU3I=Sw^(`Y1!=V6bpMK@_*G?+U$^E!I9OilWl%?EJYLf|oL z2Ad_jCg}%{3L)+Y92d8P_16wH@M~Rk6e`YFd$z6syn6_kK!B@nfpG5G)U5ZRP!wzu zz<5Mucyw`^9>D_a-F6uGlB@0 zE#|JfFfQa_ItI&GYiZal9#I_)`5qJHexTQZ5G+@@;U*bvVSR&Vn;hM$HyCkIDJtRh zgEb{$H4X1qt-KFUVB(;Nb!i92*&T`N8#A*?vKAt@S+o1qk1blu?Vm=shzt%-dhy~n z)J_29H*NsfS(jDNQKD1go)nSK>aWW2cEd5AqqQ#&Qar(}stH82dprjH&b08V<_i8z zlxQG{iN!CLBTK=lYPW;N&*cEv2Vuo|^xJ?P#_jP4vLkxh^qNE}2?paOY@DU)_X~VZ z71eqpj}<1r{9mr2E1iFOOd2*r`6vN{;Z&ETrT(9cN{ejSd<$>P;E0bc$C=J42i*7R z^P;6F=5@Zx&T~GzP@;HRMKIu zB$yy5GhtG27J`|XSw_0vpswYRVt~$0-d2;v$R=sg{kZva zZ78AMrk>5O>0~}XY^4m%*^k>!Luz<+)JcP3eauEQkS4hyYZ+{uo@)|V@dzJmz(jgo zqZ4D};;=BIrzzhNGf>wvv-2r(SR zWlq^d{zx!lCcfO#{+yZWOlnhHHgph?sz6hYpgI|}#0>DR`CP`W_bq4iNi`~HxWWyl z5~Z4hlXAS>m*m$wvYNav+vku|)hcO310{DfceFA_UI1~+(T+$Z`KHVVkePV&W?dir zOc*0Yrjq;7qmEZZ2Z}ugHG4U$oA&W~(9E6HoqL#ZU{DV>;+TB=l=0y--L5CG=U%d3 zG~b4Oi3Oh%OyXcyIzI+n$517beiqv;(WkfY`fEmqU7Ju{%t|WiNcFCoHg~~nhdy9v zG(CfUdiRC1a2$Wa(v8ooerAr)3eSdRj(}C8T`+NLDwGwpD#0yvBq!A>aveTwSW}O< z#mS>2;w6Kj-T8ayp1ry}Dv@WJYPy{@WxV68c|lkyAmFjswR^bu)8=sDd|lXMxhMV4 zN>@6f%CLafxIXlFY{UX@4Wb)ZPmsm0xLU2m&D=YrulRu`P2tkNy)(fTpzOU5CB6Uh zTb>^FGUvWBT3e;qE76&XM7qlP@j-pj>w-nj3^|P5eiedjzWt6IT#YoUR4<}f@7eAh z;?4E0!+w9HqxZ|!X&xt!)y1)Q>J4|21_4Wb)*pj=!b4C0f}lX)pYQa~@zIcW0b-6X z8+tRk2eZjYP`fUg0(H*TQqgw}UUZe=3NJUxpCQ#nz7Pa$iaQR3v^SiD?9n<-EL8lF z9ao5-;uZNNx~^eOQyHucQ&Y4_P{36BTRl$*r;Y5%#7evThCewWIwmxhfaAHnl5`~0 zrX&{97*4*4NiMYB>A;H@ExCc8iDhxjw&C)+-y80{6{6 zJiVu$m1D^e$BaV;;6&3Qj^pmqZ%d3y_hYue2a+OC<78A6N_jB1lama+uM;z|K(vu0 zQ9=oSA(=|o5mtYOo0wg$8)=UNrE1L}e5p<=*@NKLeB~J*d2aCQjy_2=S|OXt!K9ns z`;VC~;wvxVZE6$kVTq#~3EO}JpCc~y{d;=el%gOEudFS9>WNCL9Uv`%waSe2o8KW* zy(#zn&%Wn)G%T22-5Jm}9K|XS)?2uz{<%N0516$Zk3KGhuI)OYliTc8JK;gde~fVy zCDc;#gk(hZZec(3ogHBAh$hS+s-FAU9qfAURbf=L`g+V#Q4w`UDyi%t7y06#F}faM zR$qNrwOLpk$9UV(e3)4gIGm388DsZ(W02wmpD%rWCu2V_4fA`p^u+tq>3*Si&ebm@ zPpiIP#*5(bemA)X&6b^pQKG2E)}{8UFg7p{_;jr_G63Ei^q9gzn6Uy4spzFfc5W#D=BKeB9!rV?PC8yob%D>sh=r4MC zKtRffcN@b?)54^uu(J3U3iB4--j<;RXrDh`YDR@CX;QbDa+CuBZqlInsyq6mSkojq zIx9uU7-MPh432qctgzG5fYw)6>5gm#Unil!TeQw(V009|=6(CKeS;Y*L5%JH zNVg=TMN7J{F+XBCbW5;)=>p(?-kNJIaiA+`yrLe&Ky_6I1zt*Q-X1Chflo_9?gxqJ zyN{nW#T?T%t#S#&9*{m``x2J|s0uF=ap|XC1TT-FDeH9({wE|7@%{w7sK0jjO|K|2 zkfDRTiK9J_cpP0tAB>&cmnwO;btvxNcHO62GzLaII2XipczB&7T~#thsF)rxeJ?O|i7q#rjdLpOSHl;x*M5{6a8$nG~B}((tpQ zQa}HuOtgK=^I_%tweM&kg+x4i|BrcSXfBx%dz=WGjT&D%)kdi>fQ^0daBmU zZ`OQY29P!*clOOu)(Uv7(rK)FGuLR)SeL`2;kD*lM86m$Mnn^g?W&d3 zzHQ4iHq0=3DDrmk&j-`lqxs!!MFfgTXL09Zn{$k#cko*L>Ae6`5L)+s5l(boJpWJ> ztV&|yMm3J!Xdp$@@wcsQ0*q~rXgbP|hooyP>Lq9P>LDNRnr9(FO&pIXtlbp#xh8;wE8+oGpL|A8(fp*YvlNKe-L zMa`#j-xIX=WkZ1qpFBvcD|L8u((D)au=Ek?CQ*uOzh81xS|XnoTQzJZqHn?qSC)#F zIP|`vJGetTmL-C2S63KP6dTbyN^9N!$iWTFta99gkcF~V8W~!^(Fco01r@gsc&-WV zc;9Bv&X&qHe|VKeM$K3(Hym3WHimt4HE^2SNN&dW8-cEVwn;FMo4R;Y@J#2R?k&2* z+x4hmd{pR!80F=oYj2#t@%!myxVcY<7F})mZJ|zw_cxzW<5IV6zy9=?l>DjiS(}J#()F!X)m-2X8g^gM0HxR>0dr;AOUzsm7OE^^rzmlK2<}MW?n+e=$*HEz#G_?iy_D zJxTr2;G`TaJ?eFPPL)*>w~0`UvSu?Av(7(q_C^9M0H0r;yZOVIwO&QL5}kU3;>3EV z3zZUW+n7nY?C-b3yfDP)GRRfz4uHgTehGG|6@A$o%2jBtI>5?z4u)a zB*%4*SRc{5o^YVsU>cYH7YV;tTs{etHaM7?1?oNa z7G4NQuX1kXu2xSs9S(P}Y<^i6DqI(`j=iT(%T$!%^_)!Jz#)D1qh8*tPOK`sW&2gJ zQ-x5BZ=y2h@xE7E@qS$R&x?Q5IYAeu;a3~yYuGcRWs;qm+MnZBHCDxDJpFV_nXav} zw+Q(AwjVin?gt2b-SHBB0kF(@7Wk22)y1kyD>>6fck3bj4WZ(1MLpG7PG1HYowcM? zFe-O4DqX|IvmzF-^_n5KOD)JkiToSkes~}Qs0MkL>LRY9bR%9;Nz8^$`~=03`T0M| zTU&z;CWXGHGLNa4O%M6k|31OnMJYDXvF}&BEVpQ6uDB8&N{e zyV+%JSmW9J9#d+T!6UE~6>*_q|siIZdHVrgqp{D60Iy!?WcPcm5wS4&)@_FC$N1`&+x&h$;LU z5^`m|#H`k=w_NYgcRD)WbV^e&szxhSDZ7ny)Y16{DJo`n(U6rL1Ap#1lKg9=uW{me ze@0)^glD9cA5G>)QoprFb?)5I8WU7&oRKyx!mbc zLYy)lz608-(N$izEQZ7$KBha_>HPlk-Ff-xqw)zgDvhYn9G!a;GAgiSa=rCA34Gu) z@%*M8YHX_({{9=<#y5rW(pbXc<~Mf9_Ez9+OGPH*t9DBCkxresEj&`)9*%Xh0B8 zFA(Eeh$868hGD?q%#ZEmOBxQ0TF--SpN5mG0Rf4l?(QVT(5Gct$j4h&apM;U6<3I9!&u*P61Ksd$cN&{9;6!qHw{RngHy0diaOhFuWO#n% z(x2_qups?SFlgZ_igB*oP)jRV&%$C2d@LUPQ0p;9zb{28Y!;)KIMaw8Rhzb+qloVn;sg%b$}Vqt^P!Go0BA^_ z9A`7SYaxIZ8eAzt8hnWx!^l34e)|oY6C*-ol5yN|-^3VuPrDa<`@a_7?bgAR`->MX z2SrPh9@hr9l06zjgP%f#I{BgjG)Lb@9Q`p?4T|JDsY*V{cd@Kjtc--8^vAb zP9Lk{RNyFsox4xSoa-CSoJ;k&B)(Dgt@51$Nex{9zGLM2Kaj)T=ys+6qoeU}ceRZb zPlMW)G?%qtrEwFb^fG?Icd=m}Ijn=I6F&bayMH-~vb*gyfn20n^P@v}<&|X=x)U>E z?+L_A56VNnyhj$qv0iC3FX%!RMSU0(P$p+(2fw;o&`@NFa!K0RTOqDJs3{sZzFHKZ zCwB2aF)3eTL{az;;_2^}w=N_=gN{Nkzx|&^t~;*Dq+2JUg(3u31QZBG6r@||kkA*G zDgr{JDj-eTihz)W-kS)aH+Ka@nn*99gd#|l5(vGB(n2xR0AJkw?zi5(`@8r4_s*Gl z=Y7tc`Moo9&Uxl(k7K-gKQ%qQB?729h#%kLCI{k^x6Zac{f643sv2$I?CdrkW4~qv?GeaGI3NwjJM=abB?+L&@WRr zKGuYLdOe|kGQ68j%=i{Ve*7WY9;57TUfIlI+1*}j_agd}KKE6w1cqh+$qZ1AUIEI= zsC}huTSa|$W~Uu=F3lW&;ykO{b4^>$C%C$u8EEFv&56>2&L?7We{D5# ztk;iv#l&f_3;4Zv;ISVn@{oV;X4#2dw1{}*JKwJZ_iyQ@<~8YcTmsBL2n+VPCj|`1 zfY)2{cz6mu{v?t!DeM@a+DGT-o2vj&)?&_#NZyx2{t-g6#uI(wK;#gb5!pcvPJ1DJ z9AbP;_S?gIYpFA^ON#)eY~1;m*PD55g6<#k&d56{FMsWN?RGie!&gl?v#_#w?ec1Z zWEA7%bVR|NkDjgJ*@~JB^%!a^DlCv>W#yx5eL^7#DU_e)t(wC47l>C!8jZYnH1eME=%U%<-J9yZoS-muL&YVy)l?%zOjGv@I%(J# z8>0&ZkKY4OM>04%DKA7a&Hm&Vp1U{V4<{g-U@!+z*P!-o8vN8@-M6=m)9bZI-1s!H zQV5^fuDJ#0j&kbB+cG$|sEIE?pBZ)-UxeFS^(qH{5dWImmmsrrZd*}Ff5yVs`3OkI z)iQGlHuEXrS+7{l!C2XFfTf?`skliolDor+v3NR{xmO6^VK0%jh{(wiRVAZNNbG9& z>pbmjzOx7yuzQov8k}Z|42d<*MIMsOD-Iv_d#@6gv}4Vsyw@gnsn#%j51!G1V1Y%H zuvOUBUtuezlat7F(0k{R*oic{@|l`U{xE^Q|~w^*V92A=JAuuo`n%a<#8Sw3n&BC|Jsex81^yRqm(NWf5-p$CbE zwb7$42GJHiM>KEuqT(ljzbIQ9qtbBK`*;Sf!?x)aa#eIwEWcnuEEPeN6y3Wa#>FOV zdlV`peSze!UknOj*tvVMkx)ThvQmsgm`-E; zbkNnY2f>s)xW*tZt#u1MC9@Df|6FJ*u~MlnEk6c2QCBUnchOa!l@EP0b!RwM^rpz& z&Ui|0@UEdIPlmGB)k?j_L}PhJK=IhJw>Nye!7MhsjNMmgv8$X>+)k$YYg;Gs5QM-l zse9{T=UqCsE%0vFGCPq?mFJF4GmOmSp)?1;iW&dizV(s}M8z9MOY8kCLoJR4v7611 zIG`OFZnqy@WB=z!2WdR05wV#5Otk|UoKP@;WEohRrf4U92 zk3}juPn2>tG_JwLCv(jF#-uCGIk>OqE%0Q*4X~E4#9h9jFS^V)v`#KYL^6(Invqc*QshyDI-T3b?#XwD_E4J4!V+*k^J_;i|x?du``Zs0NMd z7l^W706H6M(;nus#o(uNPtYKZ;Nb~OGHa0*4FQYAx_xqw`tYONFJ#+!oRg%kpofS$=UAu~pPNUSW|cAHoGp*!sWahUn^%%vHOSa(_znWN4zFpAB zYvLM1hR{(Yo+=LCC5o_ec=Zy&ZDOuVy*Oe@T3|#H`XJ0veZ# z_hvvec$d`Pouk8YZtWFouB~!UtcTIVbu6 znv0dGC0>D^-ZHQ}eEKo8~dI4oaEmQAVy?aeeqf{!4 zA63YG3L{h&D2ci}nRM@Zi1!@|^LnZgw9p6+M zjIsPx$R!0^y;ic7aUHqIk31#TN}rfjcCx=qDJiqaHi{!0{ z0G+j7?(JxZ1goHLkLx?lSaEB{W~g=@g_x8LcWiew3VS)*NMg(W4w(9+pFnR{kH!%z zczEHb8#2>ZN|#?@w1g_DuOQ<|0N{>qcGbbLp?7=dN<~kVQw90sM}zs*4&q`-n=Ptv zwzkiXzl>p^*QjT>I=dM;ifKGmGtriyTqZL9cyclam4evz=pQMnrQ@Z-07sKgBn`4& zO>zl}Q`s(aTk|x-@(xjceoJFvXAiE;aI8ev>C~Xs8pZ&Sad#ZCN*wNU?pQGHVEI-{ zQndqfwRGtzL%^+PS7S;ncK8Te<175?N9mMU8_5&#az4eDE$53c8Txyf$7&;#-VOmC zlJ4Qf24stnRn|ZujSO>jZTbr=?hxjZ0sq0H=kr~O^YYN<8ad~P&|4e} zuFHF`Vc6J+rtG%ilobKRD5J&*U_leR0ZZw#54A{d^4HBvDzq{gt`b@;4*4V9dWH^|#!#x1B zYUdwvp<5Gh9CQXMLL%d{eH6tRDZkjTE1j}vhMrJ2m>^T}S^3lkXB!hO+aDFQ?DSL; zSJKzs+BKZIE>1=bQXH+9jbOFSoIpyaF&+T63zU=3uco=JxqtP&)o}WR;sgb^$pV7; zBBhvfgKRgcBtu&NEDlf!t&mPA=P@h=rzE9BAoYP;tbewRp_1fy!SNqFAPZENnw-u1 zk^jT|3i_# zi+2?PMpm7TQX~=YfSaHnH2!)QoLG4 z5)WrmkJuXiW3j&}|AS6{T5yivW-e6SD{FLwrENrF_MeRN&rm5-V+14RrgIWyF&p#e ztorAM|DG1#hsr%sa!WP8uHR{u&Sn)u{QnOBa2x)NN(XHS7am%0pxn3!(ALmXFIBY; F`WKL_3-tg1 diff --git a/src/assets/build/atlas.png b/src/assets/build/atlas.png new file mode 100644 index 0000000000000000000000000000000000000000..92829281d29da54d9adb80e80304c2e948ff2f85 GIT binary patch literal 21067 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 literal 0 HcmV?d00001 diff --git a/src/assets/icon_128.ico b/src/assets/build/icon_128.ico similarity index 100% rename from src/assets/icon_128.ico rename to src/assets/build/icon_128.ico diff --git a/src/assets/icon_16.ico b/src/assets/build/icon_16.ico similarity index 100% rename from src/assets/icon_16.ico rename to src/assets/build/icon_16.ico diff --git a/src/assets/icon_256.ico b/src/assets/build/icon_256.ico similarity index 100% rename from src/assets/icon_256.ico rename to src/assets/build/icon_256.ico diff --git a/src/assets/icon_32.ico b/src/assets/build/icon_32.ico similarity index 100% rename from src/assets/icon_32.ico rename to src/assets/build/icon_32.ico diff --git a/src/assets/icon_48.ico b/src/assets/build/icon_48.ico similarity index 100% rename from src/assets/icon_48.ico rename to src/assets/build/icon_48.ico diff --git a/src/assets/icon_64.ico b/src/assets/build/icon_64.ico similarity index 100% rename from src/assets/icon_64.ico rename to src/assets/build/icon_64.ico diff --git a/src/assets/unicode.hex b/src/assets/build/unicode.hex similarity index 100% rename from src/assets/unicode.hex rename to src/assets/build/unicode.hex diff --git a/src/assets/11.hex b/src/assets/discs/11.hex similarity index 100% rename from src/assets/11.hex rename to src/assets/discs/11.hex diff --git a/src/assets/5.hex b/src/assets/discs/5.hex similarity index 100% rename from src/assets/5.hex rename to src/assets/discs/5.hex diff --git a/src/assets/mellohi.hex b/src/assets/discs/mellohi.hex similarity index 100% rename from src/assets/mellohi.hex rename to src/assets/discs/mellohi.hex diff --git a/src/assets/otherside.hex b/src/assets/discs/otherside.hex similarity index 100% rename from src/assets/otherside.hex rename to src/assets/discs/otherside.hex diff --git a/src/assets/pigstep.hex b/src/assets/discs/pigstep.hex similarity index 100% rename from src/assets/pigstep.hex rename to src/assets/discs/pigstep.hex diff --git a/src/assets/relic.hex b/src/assets/discs/relic.hex similarity index 100% rename from src/assets/relic.hex rename to src/assets/discs/relic.hex diff --git a/src/assets/stal.hex b/src/assets/discs/stal.hex similarity index 100% rename from src/assets/stal.hex rename to src/assets/discs/stal.hex diff --git a/src/assets/ward.hex b/src/assets/discs/ward.hex similarity index 100% rename from src/assets/ward.hex rename to src/assets/discs/ward.hex diff --git a/src/decoder.rs b/src/decoder.rs index b7f8654..bf3db3c 100644 --- a/src/decoder.rs +++ b/src/decoder.rs @@ -1,23 +1,32 @@ use std::intrinsics::likely; use compact_str::CompactString; +use crate::elements::compound::CompoundMap; +use crate::SortAlgorithm; pub struct Decoder { pub data: *const u8, end: *const u8, + sort: SortAlgorithm, } #[allow(improper_ctypes_definitions)] impl Decoder { #[inline] #[optimize(speed)] - pub const fn new(data: &[u8]) -> Self { + pub const fn new(data: &[u8], sort: SortAlgorithm) -> Self { Self { end: unsafe { data.as_ptr().add(data.len()) }, data: data.as_ptr(), + sort, } } + #[inline] + pub fn sort(&self, map: &mut CompoundMap) { + self.sort.sort(map) + } + #[inline] #[optimize(speed)] #[must_use] diff --git a/src/element_action.rs b/src/element_action.rs index 2fa9e97..b171191 100644 --- a/src/element_action.rs +++ b/src/element_action.rs @@ -2,14 +2,17 @@ use std::cmp::Ordering; use std::convert::identity; #[cfg(not(target_arch = "wasm32"))] use std::fs::OpenOptions; +#[cfg(not(target_arch = "wasm32"))] use std::process::Command; use compact_str::CompactString; +#[cfg(not(target_arch = "wasm32"))] use notify::{EventKind, PollWatcher, RecursiveMode, Watcher}; use uuid::Uuid; -use crate::{Bookmark, panic_unchecked, set_clipboard}; -use crate::{FileUpdateSubscription, FileUpdateSubscriptionType, assets::{OPEN_ARRAY_IN_HEX_UV, OPEN_IN_TXT}}; +use crate::{Bookmark, panic_unchecked, set_clipboard, FileUpdateSubscription}; +#[cfg(not(target_arch = "wasm32"))] +use crate::{FileUpdateSubscriptionType, assets::{OPEN_ARRAY_IN_HEX_UV, OPEN_IN_TXT}}; use crate::assets::{ACTION_WHEEL_Z, COPY_FORMATTED_UV, COPY_RAW_UV, SORT_COMPOUND_BY_NAME, SORT_COMPOUND_BY_TYPE}; use crate::elements::chunk::NbtChunk; use crate::elements::compound::NbtCompound; @@ -76,9 +79,42 @@ impl ElementAction { } } + #[must_use] + pub fn by_name(a: (&str, &NbtElement), b: (&str, &NbtElement)) -> Ordering { + let (a_str, _) = a; + let (b_str, _) = b; + a_str.cmp(b_str) + } + + #[must_use] + pub fn by_type(a: (&str, &NbtElement), b: (&str, &NbtElement)) -> Ordering { + const ORDERING: [usize; 256] = { + let mut array = [usize::MAX; 256]; + array[NbtChunk::ID as usize] = 0; + array[NbtCompound::ID as usize] = 1; + array[NbtList::ID as usize] = 2; + array[NbtLongArray::ID as usize] = 3; + array[NbtIntArray::ID as usize] = 4; + array[NbtByteArray::ID as usize] = 5; + array[NbtString::ID as usize] = 6; + array[NbtDouble::ID as usize] = 7; + array[NbtFloat::ID as usize] = 8; + array[NbtLong::ID as usize] = 9; + array[NbtInt::ID as usize] = 10; + array[NbtShort::ID as usize] = 11; + array[NbtByte::ID as usize] = 12; + array + }; + + let (a_str, a_nbt) = a; + let (b_str, b_nbt) = b; + ORDERING[a_nbt.id() as usize].cmp(&ORDERING[b_nbt.id() as usize]).then_with(|| a_str.cmp(b_str)) + } + #[allow(clippy::too_many_lines)] pub fn apply(self, key: Option, indices: Box<[usize]>, tab_uuid: Uuid, true_line_number: usize, line_number: usize, element: &mut NbtElement, bookmarks: &mut Vec, subscription: &mut Option) -> Option { #[must_use] + #[cfg(not(target_arch = "wasm32"))] fn open_file(str: &str) -> bool { if cfg!(target_os = "windows") { Command::new("cmd").args(["/c", "start", str]).status() @@ -89,38 +125,6 @@ impl ElementAction { }.is_ok() } - #[must_use] - fn by_name(a: (&str, &NbtElement), b: (&str, &NbtElement)) -> Ordering { - let (a_str, _) = a; - let (b_str, _) = b; - a_str.cmp(b_str) - } - - #[must_use] - fn by_type(a: (&str, &NbtElement), b: (&str, &NbtElement)) -> Ordering { - const ORDERING: [usize; 256] = { - let mut array = [usize::MAX; 256]; - array[NbtChunk::ID as usize] = 0; - array[NbtCompound::ID as usize] = 1; - array[NbtList::ID as usize] = 2; - array[NbtLongArray::ID as usize] = 3; - array[NbtIntArray::ID as usize] = 4; - array[NbtByteArray::ID as usize] = 5; - array[NbtString::ID as usize] = 6; - array[NbtDouble::ID as usize] = 7; - array[NbtFloat::ID as usize] = 8; - array[NbtLong::ID as usize] = 9; - array[NbtInt::ID as usize] = 10; - array[NbtShort::ID as usize] = 11; - array[NbtByte::ID as usize] = 12; - array - }; - - let (a_str, a_nbt) = a; - let (b_str, b_nbt) = b; - ORDERING[a_nbt.id() as usize].cmp(&ORDERING[b_nbt.id() as usize]).then_with(|| a_str.cmp(b_str)) - } - 'm: { match self { Self::CopyRaw => { @@ -270,9 +274,9 @@ impl ElementAction { let bookmark_end = bookmarks.binary_search(&Bookmark::new(true_line_number + element.true_height() - 1, 0)).map_or_else(identity, |x| x + 1); let bookmark_slice = if bookmark_end > bookmark_start || bookmark_end > bookmarks.len() { &mut [] } else { &mut bookmarks[bookmark_start..bookmark_end] }; let reordering_indices = if let Some(compound) = element.as_compound_mut() { - compound.entries.sort_by(by_name, line_number, true_line_number, true_height, open, bookmark_slice) + compound.entries.sort_by(Self::by_name, line_number, true_line_number, true_height, open, bookmark_slice) } else if let Some(chunk) = element.as_chunk_mut() { - chunk.entries.sort_by(by_name, line_number, true_line_number, true_height, open, bookmark_slice) + chunk.entries.sort_by(Self::by_name, line_number, true_line_number, true_height, open, bookmark_slice) } else { unsafe { panic_unchecked("Unknown element kind for compound sorting") } }; @@ -286,9 +290,9 @@ impl ElementAction { let bookmark_end = bookmarks.binary_search(&Bookmark::new(true_line_number + element.true_height() - 1, 0)).map_or_else(identity, |x| x + 1); let bookmark_slice = if bookmark_end < bookmark_start || bookmark_end > bookmarks.len() { &mut [] } else { &mut bookmarks[bookmark_start..bookmark_end] }; let reordering_indices = if let Some(compound) = element.as_compound_mut() { - compound.entries.sort_by(by_type, line_number, true_line_number, true_height, open, bookmark_slice) + compound.entries.sort_by(Self::by_type, line_number, true_line_number, true_height, open, bookmark_slice) } else if let Some(chunk) = element.as_chunk_mut() { - chunk.entries.sort_by(by_type, line_number, true_line_number, true_height, open, bookmark_slice) + chunk.entries.sort_by(Self::by_type, line_number, true_line_number, true_height, open, bookmark_slice) } else { unsafe { panic_unchecked("Unknown element kind for compound sorting") } }; diff --git a/src/elements/array.rs b/src/elements/array.rs index 05d48f4..eb809a3 100644 --- a/src/elements/array.rs +++ b/src/elements/array.rs @@ -42,7 +42,7 @@ macro_rules! array { pub const ID: u8 = $my_id; #[inline] - pub(in $crate::elements) fn from_str0(mut s: &str) -> Option<(&str, Self)> { + pub(in $crate::elements) fn from_str0(mut s: &str, sort: SortAlgorithm) -> Option<(&str, Self)> { s = s .strip_prefix('[')? .trim_start() @@ -50,7 +50,7 @@ macro_rules! array { .trim_start(); let mut array = Self::new(); while !s.starts_with(']') { - let (s2, element) = NbtElement::from_str0(s)?; + let (s2, element) = NbtElement::from_str0(s, sort)?; array.insert(array.len(), element).ok()?; s = s2.trim_start(); if let Some(s2) = s.strip_prefix(',') { diff --git a/src/elements/chunk.rs b/src/elements/chunk.rs index d2ff3cc..f3ef117 100644 --- a/src/elements/chunk.rs +++ b/src/elements/chunk.rs @@ -3,6 +3,7 @@ use std::fmt::{Debug, Display, Formatter}; use std::intrinsics::likely; use std::mem::{ManuallyDrop, MaybeUninit}; use std::ops::{Deref, DerefMut}; +#[cfg(not(target_arch = "wasm32"))] use std::thread::Scope; use compact_str::{format_compact, CompactString, ToCompactString}; @@ -15,7 +16,7 @@ use crate::elements::list::{ValueIterator, ValueMutIterator}; use crate::encoder::UncheckedBufWriter; use crate::tab::FileFormat; use crate::vertex_buffer_builder::VertexBufferBuilder; -use crate::{DropFn, RenderContext, StrExt}; +use crate::{DropFn, RenderContext, SortAlgorithm, StrExt}; use crate::color::TextColor; #[repr(C)] @@ -76,8 +77,8 @@ impl NbtRegion { pub fn new() -> Self { Self::default() } #[must_use] - pub fn from_bytes(bytes: &[u8]) -> Option { - fn parse(raw: u32, bytes: &[u8]) -> Option<(FileFormat, NbtElement)> { + pub fn from_bytes(bytes: &[u8], sort: SortAlgorithm) -> Option { + fn parse(raw: u32, bytes: &[u8], sort: SortAlgorithm) -> Option<(FileFormat, NbtElement)> { if raw < 512 { return Some((FileFormat::Zlib, unsafe { core::mem::zeroed() })) } let len = (raw as usize & 0xFF) * 4096; @@ -96,6 +97,7 @@ impl NbtRegion { &DeflateDecoder::new_with_options(data, DeflateOptions::default().set_confirm_checksum(false)) .decode_gzip() .ok()?, + sort, )?, ), 2 => ( @@ -104,10 +106,11 @@ impl NbtRegion { &DeflateDecoder::new_with_options(data, DeflateOptions::default().set_confirm_checksum(false)) .decode_zlib() .ok()?, + sort, )?, ), - 3 => (FileFormat::Nbt, NbtElement::from_file(data)?), - 4 => (FileFormat::ChunkLz4, NbtElement::from_file(&lz4_flex::decompress(data, data.len()).ok()?)?), + 3 => (FileFormat::Nbt, NbtElement::from_file(data, sort)?), + 4 => (FileFormat::ChunkLz4, NbtElement::from_file(&lz4_flex::decompress(data, data.len()).ok()?, sort)?), _ => return None, }; if element.id() != NbtCompound::ID { return None } @@ -134,7 +137,7 @@ impl NbtRegion { { let timestamp = u32::from_be_bytes(timestamp); let offset = u32::from_be_bytes(offset); - threads.push((timestamp, s.spawn(move || parse(offset, bytes)))); + threads.push((timestamp, s.spawn(move || parse(offset, bytes, sort)))); } @@ -174,7 +177,7 @@ impl NbtRegion { { let timestamp = u32::from_be_bytes(timestamp); let offset = u32::from_be_bytes(offset); - threads.push((timestamp, parse(offset, bytes))); + threads.push((timestamp, parse(offset, bytes, sort))); } diff --git a/src/elements/compound.rs b/src/elements/compound.rs index 2c10511..cec4804 100644 --- a/src/elements/compound.rs +++ b/src/elements/compound.rs @@ -5,6 +5,7 @@ use std::fmt::{Debug, Display, Formatter, Write}; use std::hash::Hasher; use std::intrinsics::likely; use std::ops::Deref; +#[cfg(not(target_arch = "wasm32"))] use std::thread::Scope; use compact_str::{format_compact, CompactString, ToCompactString}; @@ -16,7 +17,7 @@ use crate::decoder::Decoder; use crate::elements::chunk::NbtChunk; use crate::elements::element::NbtElement; use crate::encoder::UncheckedBufWriter; -use crate::{Bookmark, DropFn, OptionExt, RenderContext, StrExt, VertexBufferBuilder}; +use crate::{Bookmark, DropFn, OptionExt, RenderContext, SortAlgorithm, StrExt, VertexBufferBuilder}; use crate::color::TextColor; #[allow(clippy::module_name_repetitions)] @@ -49,13 +50,13 @@ impl Clone for NbtCompound { impl NbtCompound { pub const ID: u8 = 10; #[optimize(speed)] - pub(in crate::elements) fn from_str0(mut s: &str) -> Option<(&str, Self)> { + pub(in crate::elements) fn from_str0(mut s: &str, sort: SortAlgorithm) -> Option<(&str, Self)> { s = s.strip_prefix('{')?.trim_start(); let mut compound = Self::new(); while !s.starts_with('}') { let (key, s2) = s.snbt_string_read()?; s = s2.trim_start().strip_prefix(':')?.trim_start(); - let (s2, value) = NbtElement::from_str0(s)?; + let (s2, value) = NbtElement::from_str0(s, sort)?; compound.insert_replacing(key, value); s = s2.trim_start(); if let Some(s2) = s.strip_prefix(',') { @@ -65,6 +66,7 @@ impl NbtCompound { } } let s = s.strip_prefix('}')?; + sort.sort(&mut compound.entries); Some((s, compound)) } @@ -85,6 +87,7 @@ impl NbtCompound { }; current_element = decoder.u8(); } + decoder.sort(&mut compound.entries); Some(compound) } } diff --git a/src/elements/element.rs b/src/elements/element.rs index 768d2b1..8bc44c6 100644 --- a/src/elements/element.rs +++ b/src/elements/element.rs @@ -4,6 +4,7 @@ use std::fmt::{Debug, Display, Error, Formatter}; use std::intrinsics::likely; use std::mem::{ManuallyDrop, MaybeUninit}; use std::ops::Deref; +#[cfg(not(target_arch = "wasm32"))] use std::thread::Scope; use std::{fmt, fmt::Write}; @@ -18,10 +19,8 @@ use crate::element_action::ElementAction; use crate::elements::list::{NbtList, ValueIterator, ValueMutIterator}; use crate::elements::string::NbtString; use crate::encoder::UncheckedBufWriter; -use crate::{panic_unchecked, since_epoch}; +use crate::{panic_unchecked, since_epoch, SortAlgorithm, array, primitive, DropFn, RenderContext, StrExt, VertexBufferBuilder, TextColor, assets::JUST_OVERLAPPING_BASE_TEXT_Z}; use crate::tab::FileFormat; -use crate::{array, primitive, DropFn, RenderContext, StrExt, VertexBufferBuilder}; -use crate::{TextColor, assets::JUST_OVERLAPPING_BASE_TEXT_Z}; primitive!(BYTE_UV, { Some('b') }, NbtByte, i8, 1); primitive!(SHORT_UV, { Some('s') }, NbtShort, i16, 2); @@ -259,7 +258,7 @@ impl NbtElement { impl NbtElement { #[must_use] #[allow(clippy::should_implement_trait)] // i can't, sorry :( - pub fn from_str(mut s: &str) -> Option<(Option, Self)> { + pub fn from_str(mut s: &str, sort: SortAlgorithm) -> Option<(Option, Self)> { s = s.trim_start(); if s.is_empty() { return None } @@ -270,20 +269,20 @@ impl NbtElement { prefix }) }); - let (s, element) = Self::from_str0(s).map(|(s, x)| (s.trim_start(), x))?; + let (s, element) = Self::from_str0(s, sort).map(|(s, x)| (s.trim_start(), x))?; if !s.is_empty() { return None } Some((prefix, element)) } #[allow(clippy::too_many_lines)] - pub(in crate::elements) fn from_str0(mut s: &str) -> Option<(&str, Self)> { + pub(in crate::elements) fn from_str0(mut s: &str, sort: SortAlgorithm) -> Option<(&str, Self)> { if let Some(s2) = s.strip_prefix("false") { return Some((s2, Self::Byte(NbtByte { value: 0 }))) } if let Some(s2) = s.strip_prefix("true") { return Some((s2, Self::Byte(NbtByte { value: 1 }))) } - if s.starts_with("[B;") { return NbtByteArray::from_str0(s).map(|(s, x)| (s, Self::ByteArray(x))) } - if s.starts_with("[I;") { return NbtIntArray::from_str0(s).map(|(s, x)| (s, Self::IntArray(x))) } - if s.starts_with("[L;") { return NbtLongArray::from_str0(s).map(|(s, x)| (s, Self::LongArray(x))) } - if s.starts_with('[') { return NbtList::from_str0(s).map(|(s, x)| (s, Self::List(x))) } - if s.starts_with('{') { return NbtCompound::from_str0(s).map(|(s, x)| (s, Self::Compound(x))) } + if s.starts_with("[B;") { return NbtByteArray::from_str0(s, sort).map(|(s, x)| (s, Self::ByteArray(x))) } + if s.starts_with("[I;") { return NbtIntArray::from_str0(s, sort).map(|(s, x)| (s, Self::IntArray(x))) } + if s.starts_with("[L;") { return NbtLongArray::from_str0(s, sort).map(|(s, x)| (s, Self::LongArray(x))) } + if s.starts_with('[') { return NbtList::from_str0(s, sort).map(|(s, x)| (s, Self::List(x))) } + if s.starts_with('{') { return NbtCompound::from_str0(s, sort).map(|(s, x)| (s, Self::Compound(x))) } if s.starts_with('"') { return NbtString::from_str0(s).map(|(s, x)| (s, Self::String(x))) } if let Some(s2) = s.strip_prefix("NaN") { @@ -424,7 +423,7 @@ impl NbtElement { return None; }; s = s[digit_end_idx..].trim_start(); - let (s, inner) = NbtCompound::from_str0(s)?; + let (s, inner) = NbtCompound::from_str0(s, sort)?; ( s, Self::Chunk(NbtChunk::from_compound( @@ -524,8 +523,8 @@ impl NbtElement { #[inline] #[must_use] - pub fn from_file(bytes: &[u8]) -> Option { - let mut decoder = Decoder::new(bytes); + pub fn from_file(bytes: &[u8], sort: SortAlgorithm) -> Option { + let mut decoder = Decoder::new(bytes, sort); decoder.assert_len(3)?; unsafe { if decoder.u8() != 0x0A { return None } @@ -549,8 +548,8 @@ impl NbtElement { #[inline] #[must_use] - pub fn from_mca(bytes: &[u8]) -> Option { - NbtRegion::from_bytes(bytes).map(Self::Region) + pub fn from_mca(bytes: &[u8], sort: SortAlgorithm) -> Option { + NbtRegion::from_bytes(bytes, sort).map(Self::Region) } #[inline] diff --git a/src/elements/list.rs b/src/elements/list.rs index c04d4bc..b50a353 100644 --- a/src/elements/list.rs +++ b/src/elements/list.rs @@ -3,6 +3,7 @@ use std::alloc::{alloc, Layout}; use std::fmt::{Debug, Display, Formatter, Write}; use std::intrinsics::likely; use std::slice::{Iter, IterMut}; +#[cfg(not(target_arch = "wasm32"))] use std::thread::Scope; use crate::assets::{JUST_OVERLAPPING_BASE_TEXT_Z, BASE_Z, CONNECTION_UV, LIST_UV}; @@ -10,7 +11,7 @@ use crate::decoder::Decoder; use crate::elements::chunk::NbtChunk; use crate::elements::element::{id_to_string_name, NbtElement}; use crate::encoder::UncheckedBufWriter; -use crate::{DropFn, OptionExt, RenderContext, StrExt, VertexBufferBuilder}; +use crate::{DropFn, OptionExt, RenderContext, SortAlgorithm, StrExt, VertexBufferBuilder}; use crate::color::TextColor; #[allow(clippy::module_name_repetitions)] @@ -50,11 +51,11 @@ impl Clone for NbtList { impl NbtList { pub const ID: u8 = 9; - pub(in crate::elements) fn from_str0(mut s: &str) -> Option<(&str, Self)> { + pub(in crate::elements) fn from_str0(mut s: &str, sort: SortAlgorithm) -> Option<(&str, Self)> { s = s.strip_prefix('[')?.trim_start(); let mut list = Self::new(vec![], 0); while !s.starts_with(']') { - let (s2, element) = NbtElement::from_str0(s)?; + let (s2, element) = NbtElement::from_str0(s, sort)?; list.insert(list.len(), element).ok()?; s = s2.trim_start(); if let Some(s2) = s.strip_prefix(',') { diff --git a/src/main.rs b/src/main.rs index 665b171..e3bf454 100644 --- a/src/main.rs +++ b/src/main.rs @@ -49,25 +49,23 @@ use std::cell::UnsafeCell; use std::cmp::Ordering; use std::convert::identity; -use std::fmt::Write; +use std::fmt::{Display, Formatter, Write}; use std::rc::Rc; -use std::sync::Weak; use std::time::Duration; use compact_str::{CompactString, ToCompactString}; use static_assertions::{const_assert, const_assert_eq}; #[cfg(target_arch = "wasm32")] -use wasm_bindgen::{JsCast, prelude::wasm_bindgen}; +use wasm_bindgen::prelude::wasm_bindgen; use winit::window::Window; use elements::element::NbtElement; use vertex_buffer_builder::VertexBufferBuilder; -use crate::alert::Alert; -use crate::assets::{BASE_TEXT_Z, BASE_Z, BOOKMARK_UV, BOOKMARK_Z, END_LINE_NUMBER_SEPARATOR_UV, HEADER_SIZE, HIDDEN_BOOKMARK_UV, INSERTION_UV, INVALID_STRIPE_UV, LINE_NUMBER_SEPARATOR_UV, LINE_NUMBER_Z, SCROLLBAR_BOOKMARK_Z, SELECTED_TOGGLE_OFF_UV, SELECTED_TOGGLE_ON_UV, TEXT_UNDERLINE_UV, TOGGLE_Z, UNSELECTED_TOGGLE_OFF_UV, UNSELECTED_TOGGLE_ON_UV}; +use crate::assets::{BASE_TEXT_Z, BASE_Z, BOOKMARK_UV, BOOKMARK_Z, END_LINE_NUMBER_SEPARATOR_UV, HEADER_SIZE, HIDDEN_BOOKMARK_UV, INSERTION_UV, INVALID_STRIPE_UV, LINE_NUMBER_SEPARATOR_UV, LINE_NUMBER_Z, SCROLLBAR_BOOKMARK_Z, SELECTED_TOGGLE_OFF_UV, SELECTED_TOGGLE_ON_UV, SELECTION_UV, SORT_COMPOUND_BY_NAME, SORT_COMPOUND_BY_NOTHING, SORT_COMPOUND_BY_TYPE, STAMP_BACKDROP_UV, TEXT_UNDERLINE_UV, TOGGLE_Z, UNSELECTED_TOGGLE_OFF_UV, UNSELECTED_TOGGLE_ON_UV}; use crate::color::TextColor; use crate::elements::chunk::{NbtChunk, NbtRegion}; -use crate::elements::compound::NbtCompound; +use crate::elements::compound::{CompoundMap, NbtCompound}; use crate::elements::element::{NbtByte, NbtByteArray, NbtDouble, NbtFloat, NbtInt, NbtIntArray, NbtLong, NbtLongArray, NbtShort}; use crate::elements::list::NbtList; use crate::elements::string::NbtString; @@ -179,6 +177,7 @@ macro_rules! log { #[macro_export] macro_rules! debg { () => { + #[cfg(debug_assertions)] $crate::log!("[{}:{}:{}]", file!(), line!(), column!()) }; } @@ -205,6 +204,8 @@ pub static mut WINDOW_PROPERTIES: UnsafeCell = UnsafeCell::new #[cfg(target_arch = "wasm32")] #[wasm_bindgen] pub fn handle_dialog(name: String, bytes: Vec) { + use crate::alert::Alert; + let workbench = unsafe { WORKBENCH.get_mut() }; if let Err(e) = workbench.on_open_file(name.as_str().as_ref(), bytes, unsafe { WINDOW_PROPERTIES.get_mut() }) { @@ -232,7 +233,7 @@ pub fn main() -> ! { pollster::block_on(window::run()) } /// * wiki page for docs on minecraft's format of stuff /// * [chunk](NbtChunk) section rendering /// # Minor Features -/// * save & load for web assembly (and open icon for exe ver) +/// * open icon for exe ver /// * gear icon to swap toolbar with settings panel /// * sort entries on file read config /// * make floats either exact or "exact enough" @@ -292,43 +293,6 @@ pub fn set_clipboard(value: String) -> bool { return web_sys::window().map(|window| window.navigator()).and_then(|navigator| navigator.clipboard()).map(|clipboard| clipboard.write_text(&value)).is_some(); } -#[must_use] -pub fn encode(bytes: &[u8]) -> Vec { - let len = bytes.len(); - let mut encoded = Vec::with_capacity(((len + 2) / 3) * 4); - let mut data = 0_u32; - let mut bits = 0_u32; - let mut iter = bytes.iter(); - loop { - while bits >= 6 { - encoded.push(match (data >> 26_u32) as u8 & 63 { - x @ 0..=25 => x + b'A', - x @ 26..=51 => x + (b'a' - 26), - x @ 52..=61 => x.wrapping_add(b'0'.wrapping_sub(52)), - 62 => b'+', - 63 => b'/', - // SAFETY: do not change the & 63 without consulting the unsafe assurance - _ => unsafe { std::hint::unreachable_unchecked() }, - }); - bits -= 6; - data <<= 6_u32; - } - if let Some(&byte) = iter.next() { - data |= u32::from(byte) << (24 - bits); - bits += 8; - } else if bits > 0 { - bits = 6; - } else { - break; - } - } - while encoded.len() % 3 != 0 { - encoded.push(b'='); - } - encoded -} - - #[must_use] pub fn since_epoch() -> Duration { #[cfg(not(target_arch = "wasm32"))] @@ -534,6 +498,81 @@ pub enum FileUpdateSubscriptionType { LongArray, } +#[derive(Copy, Clone)] +pub enum SortAlgorithm { + None, + Name, + Type, +} + +impl SortAlgorithm { + pub fn render(self, builder: &mut VertexBufferBuilder, ctx: &mut RenderContext) { + let uv = match self { + Self::None => SORT_COMPOUND_BY_NOTHING, + Self::Name => SORT_COMPOUND_BY_NAME, + Self::Type => SORT_COMPOUND_BY_TYPE, + }; + + builder.draw_texture((264, 26), STAMP_BACKDROP_UV, (16, 16)); + builder.draw_texture((267, 29), uv, (10, 10)); + + let hovering = (264..280).contains(&ctx.mouse_x) && (26..42).contains(&ctx.mouse_y); + if hovering { + builder.draw_texture((264, 26), SELECTION_UV, (16, 16)); + builder.draw_tooltip(&[&format!("Compound Sorting Algorithm ({self})")], (ctx.mouse_x, ctx.mouse_y)); + } + } + + pub fn cycle(self) -> Self { + match self { + Self::None => Self::Name, + Self::Name => Self::Type, + Self::Type => Self::None, + } + } + + pub fn rev_cycle(self) -> Self { + match self { + Self::None => Self::Type, + Self::Name => Self::None, + Self::Type => Self::Name, + } + } + + pub fn sort(self, map: &mut CompoundMap) { + if let Self::None = self { return; } + let hashes = map.entries.iter().map(|entry| entry.hash).collect::>(); + // yeah, it's hacky but there's not much else I *can* do. plus: it works extremely well. + for (idx, entry) in map.entries.iter_mut().enumerate() { + entry.hash = idx as u64; + } + match self { + Self::Name => map.entries.sort_by(|a, b| element_action::ElementAction::by_name((&a.key, &a.value), (&b.key, &b.value))), + _ => map.entries.sort_by(|a, b| element_action::ElementAction::by_type((&a.key, &a.value), (&b.key, &b.value))), + } + let indices = map.entries.iter().map(|entry| entry.hash as usize).collect::>(); + for (new_idx, &idx) in indices.iter().enumerate() { + // SAFETY: these indices are valid since the length did not change and since the values written were indexes + unsafe { + let hash = *hashes.get_unchecked(idx); + let entry = map.entries.get_unchecked_mut(new_idx); + entry.hash = hash; + *map.indices.find(hash, |&x| x == idx).panic_unchecked("index obviously exists").as_mut() = new_idx; + } + } + } +} + +impl Display for SortAlgorithm { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", match self { + Self::None => "None", + Self::Name => "Name-Based", + Self::Type => "Type-Based", + }) + } +} + pub struct RenderContext { selecting_key: bool, selected_y: usize, @@ -552,12 +591,13 @@ pub struct RenderContext { pub y_offset: usize, // must be sorted least to greatest line_numbers: Vec, + freehand: bool } impl RenderContext { #[must_use] #[allow(clippy::type_complexity)] // forbidden is fine to be like that, c'mon - pub fn new(selected_y: usize, selected_key: Option>, selected_value: Option>, selecting_key: bool, ghost: Option<(u8, usize, usize)>, left_margin: usize, mouse: (usize, usize)) -> Self { + pub fn new(selected_y: usize, selected_key: Option>, selected_value: Option>, selecting_key: bool, ghost: Option<(u8, usize, usize)>, left_margin: usize, mouse: (usize, usize), freehand: bool) -> Self { Self { selecting_key, selected_y, @@ -575,6 +615,7 @@ impl RenderContext { x_offset: 16 + left_margin, y_offset: HEADER_SIZE, line_numbers: vec![], + freehand, } } @@ -608,7 +649,7 @@ impl RenderContext { let x = (pos.0 - self.left_margin) / 16; let y = (pos.1 - HEADER_SIZE) / 16; let hovered = if (self.mouse_x >= self.left_margin) & (self.mouse_y >= HEADER_SIZE) { - (x >= (self.mouse_x - self.left_margin) / 16) & (y == (self.mouse_y - HEADER_SIZE) / 16) + ((x >= (self.mouse_x - self.left_margin) / 16) || self.freehand) & (y == (self.mouse_y - HEADER_SIZE) / 16) } else { false }; @@ -869,18 +910,18 @@ impl LinkedQueue { } #[must_use] - pub fn iter(&self) -> LinkedQueueIterator<'_, T> { - LinkedQueueIterator { + pub fn iter(&self) -> LinkedQueueIter<'_, T> { + LinkedQueueIter { tail: &self.tail, } } } -pub struct LinkedQueueIterator<'a, T> { +pub struct LinkedQueueIter<'a, T> { tail: &'a Option>>, } -impl<'a, T> Iterator for LinkedQueueIterator<'a, T> { +impl<'a, T> Iterator for LinkedQueueIter<'a, T> { type Item = &'a T; fn next(&mut self) -> Option { @@ -1154,7 +1195,7 @@ impl StrExt for str { pub trait OptionExt { /// # Safety /// - /// * This code better be unreachable otherwise it's UB without `debug_assertions`, just a panic with them however. + /// * This code better be unreachable otherwise it's UB without `debug_assertions`, just a panic with them, however. unsafe fn panic_unchecked(self, msg: &str) -> T; #[allow(clippy::wrong_self_convention)] // then why is is_some_and like that, huh? @@ -1169,7 +1210,7 @@ impl OptionExt for Option { /// # Safety /// -/// * This code better be unreachable otherwise it's UB without `debug_assertions`, just a panic with them however. +/// * This code better be unreachable otherwise it's UB without `debug_assertions`, just a panic with them, however. /// /// # Panics /// @@ -1197,18 +1238,3 @@ const_assert_eq!( VertexBufferBuilder::CHAR_WIDTH[b':' as usize], VertexBufferBuilder::CHAR_WIDTH[b',' as usize] ); - -const_assert!(core::mem::size_of::() <= core::mem::size_of::()); -const_assert!(core::mem::size_of::() <= core::mem::size_of::()); -const_assert!(core::mem::size_of::() <= core::mem::size_of::()); -const_assert!(core::mem::size_of::() <= core::mem::size_of::()); -const_assert!(core::mem::size_of::() <= core::mem::size_of::()); -const_assert!(core::mem::size_of::() <= core::mem::size_of::()); -const_assert!(core::mem::size_of::() <= core::mem::size_of::()); -const_assert!(core::mem::size_of::() <= core::mem::size_of::()); -const_assert!(core::mem::size_of::() <= core::mem::size_of::()); -const_assert!(core::mem::size_of::() <= core::mem::size_of::()); -const_assert!(core::mem::size_of::() <= core::mem::size_of::()); -const_assert!(core::mem::size_of::() <= core::mem::size_of::()); -const_assert!(core::mem::size_of::() <= core::mem::size_of::()); -const_assert!(core::mem::size_of::() <= core::mem::size_of::()); diff --git a/src/selected_text.rs b/src/selected_text.rs index 303143d..2d67403 100644 --- a/src/selected_text.rs +++ b/src/selected_text.rs @@ -7,7 +7,7 @@ use winit::keyboard::KeyCode; use crate::assets::{BASE_TEXT_Z, ELEMENT_HIGHLIGHT_Z, HEADER_SIZE, SELECTED_TEXT_Z, SELECTION_UV}; use crate::selected_text::KeyResult::{Down, Failed, Finish, ForceClose, ForceOpen, Keyfix, NothingSpecial, Revert, ShiftDown, ShiftUp, Up, Valuefix}; use crate::vertex_buffer_builder::VertexBufferBuilder; -use crate::{flags, get_clipboard, is_jump_char_boundary, is_utf8_char_boundary, LinkedQueue, OptionExt, set_clipboard, since_epoch, StrExt, WindowProperties}; +use crate::{flags, get_clipboard, is_jump_char_boundary, is_utf8_char_boundary, LinkedQueue, OptionExt, set_clipboard, since_epoch, StrExt}; use crate::color::TextColor; #[derive(Clone, Debug)] diff --git a/src/tab.rs b/src/tab.rs index 07ca661..d793ea5 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -1,5 +1,4 @@ use std::ffi::OsStr; -use std::fs::write; use std::io::Read; use std::ops::Deref; use std::path::{Path, PathBuf}; @@ -11,7 +10,7 @@ use flate2::Compression; use uuid::Uuid; use crate::{Bookmark, LinkedQueue, OptionExt, panic_unchecked, RenderContext, StrExt, WindowProperties}; -use crate::assets::{BYTE_ARRAY_GHOST_UV, BYTE_ARRAY_UV, BYTE_GRAYSCALE_UV, BYTE_UV, CHUNK_GHOST_UV, CHUNK_UV, COMPOUND_GHOST_UV, COMPOUND_ROOT_UV, COMPOUND_UV, DOUBLE_GRAYSCALE_UV, DOUBLE_UV, ENABLED_FREEHAND_MODE_UV, FLOAT_GRAYSCALE_UV, FLOAT_UV, FREEHAND_MODE_UV, GZIP_FILE_TYPE_UV, HEADER_SIZE, HELD_SCROLLBAR_UV, INT_ARRAY_GHOST_UV, INT_ARRAY_UV, INT_GRAYSCALE_UV, INT_UV, JUST_OVERLAPPING_BASE_Z, LINE_NUMBER_SEPARATOR_UV, LIST_GHOST_UV, LIST_UV, LONG_ARRAY_GHOST_UV, LONG_ARRAY_UV, LONG_GRAYSCALE_UV, LONG_UV, MCA_FILE_TYPE_UV, NBT_FILE_TYPE_UV, REDO_UV, REGION_UV, SCROLLBAR_Z, SHORT_GRAYSCALE_UV, SHORT_UV, SNBT_FILE_TYPE_UV, STEAL_ANIMATION_OVERLAY, STRING_GHOST_UV, STRING_UV, UNDO_UV, UNHELD_SCROLLBAR_UV, UNKNOWN_NBT_GHOST_UV, UNKNOWN_NBT_UV, ZLIB_FILE_TYPE_UV}; +use crate::assets::{BASE_Z, BYTE_ARRAY_GHOST_UV, BYTE_ARRAY_UV, BYTE_GRAYSCALE_UV, BYTE_UV, CHUNK_GHOST_UV, CHUNK_UV, COMPOUND_GHOST_UV, COMPOUND_ROOT_UV, COMPOUND_UV, DOUBLE_GRAYSCALE_UV, DOUBLE_UV, ENABLED_FREEHAND_MODE_UV, FLOAT_GRAYSCALE_UV, FLOAT_UV, FREEHAND_MODE_UV, GZIP_FILE_TYPE_UV, HEADER_SIZE, HELD_SCROLLBAR_UV, INT_ARRAY_GHOST_UV, INT_ARRAY_UV, INT_GRAYSCALE_UV, INT_UV, JUST_OVERLAPPING_BASE_Z, LINE_NUMBER_SEPARATOR_UV, LIST_GHOST_UV, LIST_UV, LONG_ARRAY_GHOST_UV, LONG_ARRAY_UV, LONG_GRAYSCALE_UV, LONG_UV, MCA_FILE_TYPE_UV, NBT_FILE_TYPE_UV, REDO_UV, REGION_UV, SCROLLBAR_Z, SHORT_GRAYSCALE_UV, SHORT_UV, SNBT_FILE_TYPE_UV, STEAL_ANIMATION_OVERLAY_UV, STRING_GHOST_UV, STRING_UV, UNDO_UV, UNHELD_SCROLLBAR_UV, UNKNOWN_NBT_GHOST_UV, UNKNOWN_NBT_UV, ZLIB_FILE_TYPE_UV}; use crate::color::TextColor; use crate::elements::chunk::NbtRegion; use crate::elements::compound::NbtCompound; @@ -66,8 +65,8 @@ impl Tab { } pub fn save(&mut self, force_dialog: bool) -> Result<()> { - let path = self.path.as_deref().unwrap_or(self.name.as_ref().as_ref()); #[cfg(target_os = "windows")] { + let path = self.path.as_deref().unwrap_or(self.name.as_ref().as_ref()); if !path.exists() || force_dialog { let mut builder = native_dialog::FileDialog::new(); if self.value.id() == NbtRegion::ID { @@ -77,12 +76,12 @@ impl Tab { } let path = builder.show_save_single_file()?.ok_or_else(|| anyhow!("Save cancelled"))?; self.name = path.file_name().and_then(|x| x.to_str()).expect("Path has a filename").to_string().into_boxed_str(); - write(&path, self.compression.encode(&self.value))?; + std::fs::write(&path, self.compression.encode(&self.value))?; self.path = Some(path); self.history_changed = false; Ok(()) } else { - write(path, self.compression.encode(&self.value))?; + std::fs::write(path, self.compression.encode(&self.value))?; self.history_changed = false; Ok(()) } @@ -155,18 +154,15 @@ impl Tab { { let mut tail = self.undos.tail.as_deref(); - builder.draw_texture( - (builder.window_width() - 107, 26), + builder.draw_texture_region_z( + (builder.window_width() - 109, 22), + BASE_Z, LINE_NUMBER_SEPARATOR_UV, + (2, 23), (2, 16), ); - builder.draw_texture( - (builder.window_width() - 129, 26), - LINE_NUMBER_SEPARATOR_UV, - (2, 16), - ); - builder.draw_texture((builder.window_width() - 125, 26), UNDO_UV, (16, 16)); - let mut x = builder.window_width() - 104; + builder.draw_texture((builder.window_width() - 105, 26), UNDO_UV, (16, 16)); + let mut x = builder.window_width() - 84; for _ in 0..5_usize { if let Some(t) = tail { t.value.render((x, 26), builder, t.prev.is_none()); @@ -180,18 +176,15 @@ impl Tab { { let mut tail = self.redos.tail.as_deref(); - builder.draw_texture( - (builder.window_width() - 213, 26), - LINE_NUMBER_SEPARATOR_UV, - (2, 16), - ); - builder.draw_texture( - (builder.window_width() - 235, 26), + builder.draw_texture_region_z( + (builder.window_width() - 215, 22), + BASE_Z, LINE_NUMBER_SEPARATOR_UV, + (2, 23), (2, 16), ); - builder.draw_texture((builder.window_width() - 231, 26), REDO_UV, (16, 16)); - let mut x = builder.window_width() - 210; + builder.draw_texture((builder.window_width() - 211, 26), REDO_UV, (16, 16)); + let mut x = builder.window_width() - 190; for _ in 0..5_usize { if let Some(t) = tail { t.value.render((x, 26), builder, t.prev.is_none()); @@ -204,13 +197,16 @@ impl Tab { } { - builder.draw_texture( - (builder.window_width() - 22, 26), + // shifted one left to center between clipboard and freehand + builder.draw_texture_region_z( + (244, 22), + BASE_Z, LINE_NUMBER_SEPARATOR_UV, + (2, 23), (2, 16), ); let freehand_uv = { - let hovering = (builder.window_width() - 16..builder.window_width()).contains(&mouse_x) && (26..42).contains(&mouse_y); + let hovering = (248..264).contains(&mouse_x) && (26..42).contains(&mouse_y); if hovering { builder.draw_tooltip(&["Freehand Mode (Alt + F)"], (mouse_x, mouse_y)); } @@ -225,7 +221,7 @@ impl Tab { } } }; - builder.draw_texture((builder.window_width() - 18, 26), freehand_uv, (16, 16)); + builder.draw_texture((248, 26), freehand_uv, (16, 16)); } { @@ -285,7 +281,7 @@ impl Tab { if steal_delta > 0.0 { let y = ((mouse_y - HEADER_SIZE) & !15) + HEADER_SIZE; let height = (16.0 * steal_delta).round() as usize; - builder.draw_texture_region_z((ctx.left_margin - 2, y + (16 - height)), JUST_OVERLAPPING_BASE_Z, STEAL_ANIMATION_OVERLAY, (builder.window_width() + 2 - ctx.left_margin, height), (16, 16)); + builder.draw_texture_region_z((ctx.left_margin - 2, y + (16 - height)), JUST_OVERLAPPING_BASE_Z, STEAL_ANIMATION_OVERLAY_UV, (builder.window_width() + 2 - ctx.left_margin, height), (16, 16)); } } diff --git a/src/window.rs b/src/window.rs index 1a9c3b6..47087c2 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1,6 +1,5 @@ use std::borrow::Cow; use std::cell::UnsafeCell; -use std::num::NonZeroU64; use std::rc::Rc; use std::time::Duration; #[cfg(target_arch = "wasm32")] @@ -25,7 +24,7 @@ use crate::assets::HEADER_SIZE; use crate::color::TextColor; use crate::vertex_buffer_builder::VertexBufferBuilder; use crate::workbench::Workbench; -use crate::{assets, WORKBENCH, WINDOW_PROPERTIES, debg, error, OptionExt, since_epoch, WindowProperties}; +use crate::{assets, WORKBENCH, WINDOW_PROPERTIES, error, OptionExt, since_epoch, WindowProperties}; pub const WINDOW_HEIGHT: usize = 420; pub const WINDOW_WIDTH: usize = 620; @@ -63,9 +62,10 @@ pub async fn run() -> ! { let height = window.inner_height().ok()?.as_f64()?; Some((document, PhysicalSize::new(width as u32, height as u32))) }).and_then(|(document, size)| { - let canvas = web_sys::Element::from(window.canvas()?); + let canvas = web_sys::HtmlElement::from(window.canvas()?); document.body()?.append_child(&canvas).ok()?; let _ = window.request_inner_size(size); + let _ = canvas.focus(); Some(size) }).expect("Couldn't append canvas to document body") }; @@ -99,7 +99,8 @@ pub async fn run() -> ! { Event::AboutToWait => { #[cfg(target_arch = "wasm32")] { let old_size = window.inner_size(); - let new_size: PhysicalSize = web_sys::window().map(|window| PhysicalSize::new(window.inner_width().ok().as_ref().and_then(JsValue::as_f64).expect("Width must exist") as u32, window.inner_height().ok().as_ref().and_then(JsValue::as_f64).expect("Height must exist") as u32)).expect("Window has dimension properties"); + let scaling_factor = web_sys::window().map_or(1.0, |window| window.device_pixel_ratio()); + let new_size: PhysicalSize = web_sys::window().map(|window| PhysicalSize::new((window.inner_width().ok().as_ref().and_then(JsValue::as_f64).expect("Width must exist") * scaling_factor).ceil() as u32, (window.inner_height().ok().as_ref().and_then(JsValue::as_f64).expect("Height must exist") * scaling_factor).ceil() as u32)).expect("Window has dimension properties"); if new_size != old_size { let _ = window.request_inner_size(new_size); state.resize(workbench, new_size); diff --git a/src/workbench.rs b/src/workbench.rs index c86cbfd..d5fb02d 100644 --- a/src/workbench.rs +++ b/src/workbench.rs @@ -34,7 +34,7 @@ use crate::vertex_buffer_builder::Vec2u; use crate::vertex_buffer_builder::VertexBufferBuilder; use crate::window::{MIN_WINDOW_HEIGHT, MIN_WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_WIDTH}; use crate::workbench_action::WorkbenchAction; -use crate::{encompasses, encompasses_or_equal, flags, panic_unchecked, recache_along_indices, sum_indices, Bookmark, DropFn, FileUpdateSubscription, FileUpdateSubscriptionType, HeldEntry, LinkedQueue, OptionExt, Position, RenderContext, StrExt, WindowProperties, tab, tab_mut, get_clipboard, set_clipboard, since_epoch}; +use crate::{encompasses, encompasses_or_equal, flags, panic_unchecked, recache_along_indices, sum_indices, Bookmark, DropFn, FileUpdateSubscription, FileUpdateSubscriptionType, HeldEntry, LinkedQueue, OptionExt, Position, RenderContext, StrExt, WindowProperties, tab, tab_mut, get_clipboard, set_clipboard, since_epoch, SortAlgorithm}; pub struct Workbench { pub tabs: Vec, @@ -59,6 +59,7 @@ pub struct Workbench { alerts: Vec, pub scale: usize, steal_animation_data: Option<(Duration, Vec2u)>, + sort_algorithm: SortAlgorithm, } impl Workbench { @@ -86,6 +87,7 @@ impl Workbench { alerts: vec![], scale: 0, steal_animation_data: None, + sort_algorithm: SortAlgorithm::Type, } } @@ -115,6 +117,7 @@ impl Workbench { alerts: vec![], scale: 1, steal_animation_data: None, + sort_algorithm: SortAlgorithm::Type, }; 'create_tab: { if let Some(path) = &std::env::args() @@ -129,7 +132,7 @@ impl Workbench { } workbench.new_custom_tab(window_properties, Tab { #[cfg(debug_assertions)] - value: Box::new(NbtElement::from_file(include_bytes!("assets/test.nbt")).expect("Included debug nbt contains valid data")), + value: Box::new(NbtElement::from_file(include_bytes!("assets/test.nbt"), SortAlgorithm::None).expect("Included debug nbt contains valid data")), #[cfg(debug_assertions)] name: "test.nbt".into(), #[cfg(not(debug_assertions))] @@ -164,7 +167,7 @@ impl Workbench { let (nbt, compressed) = { if path.extension().and_then(OsStr::to_str) == Some("mca") { ( - NbtElement::from_mca(buf.as_slice()).context("Failed to parse MCA file")?, + NbtElement::from_mca(buf.as_slice(), self.sort_algorithm).context("Failed to parse MCA file")?, FileFormat::Mca, ) } else if let Some(0x1F8B) = buf.first_chunk::<2>().copied().map(u16::from_be_bytes) { @@ -173,6 +176,7 @@ impl Workbench { &DeflateDecoder::new(buf.as_slice()) .decode_gzip() .context("Failed to decode gzip compressed NBT")?, + self.sort_algorithm, ) .context("Failed to parse NBT")?, FileFormat::Gzip, @@ -183,17 +187,18 @@ impl Workbench { &DeflateDecoder::new(buf.as_slice()) .decode_zlib() .context("Failed to decode zlib compressed NBT")?, + self.sort_algorithm, ) .context("Failed to parse NBT")?, FileFormat::Zlib, ) - } else if let Some(nbt) = NbtElement::from_file(buf.as_slice()) { + } else if let Some(nbt) = NbtElement::from_file(buf.as_slice(), self.sort_algorithm) { (nbt, FileFormat::Nbt) } else { ( core::str::from_utf8(&buf) .ok() - .and_then(NbtElement::from_str) + .and_then(|s| NbtElement::from_str(s, self.sort_algorithm)) .context(anyhow!( "Failed to find file type for file {}", path.file_name() @@ -329,12 +334,12 @@ impl Workbench { 'a: { let freehand_mode = tab!(self).freehand_mode; - if self.mouse_x >= left_margin && self.mouse_y >= HEADER_SIZE { + if x >= left_margin && y >= HEADER_SIZE { match self.action_wheel.take() { Some(_) => {}, None => { if button == MouseButton::Right { - self.action_wheel = Some((((self.mouse_x - left_margin) & !15) + left_margin + 6, ((self.mouse_y - HEADER_SIZE) & !15) + HEADER_SIZE + 7)); + self.action_wheel = Some((((x - left_margin) & !15) + left_margin + 6, ((y - HEADER_SIZE) & !15) + HEADER_SIZE + 7)); break 'a; } } @@ -357,16 +362,20 @@ impl Workbench { self.steal_animation_data = None; } } - if (self.window_width - 16..self.window_width).contains(&x) && (26..42).contains(&y) { + if let MouseButton::Left | MouseButton::Right = button && (248..264).contains(&x) && (26..42).contains(&y) { let tab = tab_mut!(self); tab.freehand_mode = !tab.freehand_mode; break 'a; } + if let MouseButton::Left | MouseButton::Right = button && (264..280).contains(&x) && (26..42).contains(&y) { + self.sort_algorithm = if button == MouseButton::Left { self.sort_algorithm.cycle() } else { self.sort_algorithm.rev_cycle() }; + break 'a; + } if ((self.window_width - 7)..self.window_width).contains(&x) { let tab = tab_mut!(self); let height = tab.value.height() * 16 + 48; let total = self.window_height - HEADER_SIZE; - if height > total { + if height - 48 > total { let start = total * self.scroll() / height + HEADER_SIZE; let end = start + total * total / height; if (start..=end).contains(&y) { @@ -442,10 +451,10 @@ impl Workbench { #[inline] #[allow(clippy::too_many_lines)] pub fn try_subscription(&mut self) -> Result<()> { - fn write_snbt(subscription: &FileUpdateSubscription, data: &[u8], tab: &mut Tab) -> Result<()> { + fn write_snbt(subscription: &FileUpdateSubscription, data: &[u8], tab: &mut Tab, sort: SortAlgorithm) -> Result<()> { let Some((key, value)) = core::str::from_utf8(data) .ok() - .and_then(NbtElement::from_str) + .and_then(|s| NbtElement::from_str(s, sort)) else { return Err(anyhow!("SNBT failed to parse.")); }; @@ -630,7 +639,7 @@ impl Workbench { }; match subscription.rx.try_recv() { Ok(data) => match subscription.subscription_type { - FileUpdateSubscriptionType::Snbt => write_snbt(subscription, &data, tab)?, + FileUpdateSubscriptionType::Snbt => write_snbt(subscription, &data, tab, self.sort_algorithm)?, FileUpdateSubscriptionType::ByteArray => write_array(subscription, tab, { let mut array = NbtByteArray::new(); for (idx, byte) in data.into_iter().enumerate() { @@ -1079,7 +1088,7 @@ impl Workbench { let tab = tab!(self); let x = self.mouse_x - (16 + 4); if x / 16 == 13 { - match NbtElement::from_str(&get_clipboard().ok_or_else(|| anyhow!("Failed to get clipboard"))?) { + match NbtElement::from_str(&get_clipboard().ok_or_else(|| anyhow!("Failed to get clipboard"))?, self.sort_algorithm) { Some((key, element)) => { if element.id() == NbtChunk::ID && tab.value.id() != NbtRegion::ID { return Err(anyhow!("Chunks are not supported for non-region tabs")); @@ -2076,7 +2085,7 @@ impl Workbench { // }); // return true; // } - if key == KeyCode::KeyV && flags == flags!(Ctrl) && let Some(element) = get_clipboard().and_then(|x| NbtElement::from_str(&x)) && (element.1.id() != NbtChunk::ID || tab.value.id() == NbtRegion::ID) { + if key == KeyCode::KeyV && flags == flags!(Ctrl) && let Some(element) = get_clipboard().and_then(|x| NbtElement::from_str(&x, self.sort_algorithm)) && (element.1.id() != NbtChunk::ID || tab.value.id() == NbtRegion::ID) { let old_held_entry = core::mem::replace(&mut self.held_entry, HeldEntry::FromAether(element)); let HeldEntry::FromAether(pair) = core::mem::replace(&mut self.held_entry, old_held_entry) else { unsafe { panic_unchecked("we just set it you, bozo") } @@ -2147,13 +2156,13 @@ impl Workbench { let Ok(buf) = read(path) else { break 'a }; let (nbt, compression) = { if path.extension().and_then(OsStr::to_str) == Some("mca") { - (NbtElement::from_mca(buf.as_slice()), FileFormat::Mca) + (NbtElement::from_mca(buf.as_slice(), self.sort_algorithm), FileFormat::Mca) } else if buf.first_chunk::<2>().copied().map(u16::from_be_bytes) == Some(0x1F8B) { ( DeflateDecoder::new(buf.as_slice()) .decode_gzip() .ok() - .and_then(|x| NbtElement::from_file(&x)), + .and_then(|x| NbtElement::from_file(&x, self.sort_algorithm)), FileFormat::Zlib, ) } else if let Some(0x7801 | 0x789C | 0x78DA) = buf.first_chunk::<2>().copied().map(u16::from_be_bytes) { @@ -2161,15 +2170,15 @@ impl Workbench { DeflateDecoder::new(buf.as_slice()) .decode_zlib() .ok() - .and_then(|x| NbtElement::from_file(&x)), + .and_then(|x| NbtElement::from_file(&x, self.sort_algorithm)), FileFormat::Zlib, ) - } else if let Some(nbt) = NbtElement::from_file(&buf) { + } else if let Some(nbt) = NbtElement::from_file(&buf, self.sort_algorithm) { (Some(nbt), FileFormat::Nbt) } else { let nbt = core::str::from_utf8(&buf) .ok() - .and_then(NbtElement::from_str) + .and_then(|s| NbtElement::from_str(s, self.sort_algorithm)) .map(|x| x.1); if let Some(NbtCompound::ID | NbtRegion::ID) = nbt.as_ref().map(NbtElement::id) { (nbt, FileFormat::Snbt) @@ -2397,7 +2406,7 @@ impl Workbench { } else { (None, None, false) }; - let mut ctx = RenderContext::new(selected_y, selected_key, selected_value, selecting_key, ghost, left_margin, (self.mouse_x, self.mouse_y)); + let mut ctx = RenderContext::new(selected_y, selected_key, selected_value, selecting_key, ghost, left_margin, (self.mouse_x, self.mouse_y), tab.freehand_mode); if self.mouse_y >= HEADER_SIZE && self.action_wheel.is_none() { builder.draw_texture_region_z( (0, (self.mouse_y & !15) + 1), @@ -2413,7 +2422,29 @@ impl Workbench { builder.draw_texture((0, 26), SELECTION_UV, (16, 16)); builder.draw_tooltip(&["Open File"], (self.mouse_x, self.mouse_y)); } - builder.draw_texture((17, 26), LINE_NUMBER_SEPARATOR_UV, (2, 16)); + builder.draw_texture_region_z( + (17, 22), + BASE_Z, + LINE_NUMBER_SEPARATOR_UV, + (2, 23), + (2, 16), + ); + } + { + builder.draw_texture_region_z( + (281, 22), + BASE_Z, + LINE_NUMBER_SEPARATOR_UV, + (2, 23), + (2, 16), + ); + builder.draw_texture_region_z( + (283, 23), + BASE_Z, + DARK_STRIPE_UV, + (self.window_width - 215 - 283, 22), + (16, 16), + ); } tab.render( builder, @@ -2423,6 +2454,7 @@ impl Workbench { self.action_wheel.is_some(), self.steal_animation_data.as_ref().map(|x| (since_epoch() - x.0).min(Duration::from_millis(500)).as_millis() as f32 / 500.0).unwrap_or(0.0) ); + self.sort_algorithm.render(builder, &mut ctx); if let Some(selected_text) = &tab.selected_text { builder.horizontal_scroll = horizontal_scroll; selected_text.render(builder, left_margin);