From 8a1f40cb62915563a0484d6f4248b79cf51e8475 Mon Sep 17 00:00:00 2001 From: Rokas Puzonas Date: Wed, 13 Sep 2023 23:55:06 +0300 Subject: [PATCH] add simple memory viewer --- .gitmodules | 3 + build.zig | 11 +- build.zig.zon | 2 +- libs/raylib | 1 + src/ROMs/br8kout.ch8 | Bin 0 -> 199 bytes src/chip.zig | 15 +- src/fonts/generic-mono.otf | Bin 0 -> 34180 bytes src/main.zig | 655 ++++++++++++++++++++++++++++++++++++- src/raylib-chip.zig | 63 ++-- 9 files changed, 689 insertions(+), 61 deletions(-) create mode 160000 libs/raylib create mode 100644 src/ROMs/br8kout.ch8 create mode 100644 src/fonts/generic-mono.otf diff --git a/.gitmodules b/.gitmodules index bad17c2..eeb9fe5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "libs/raylib-zig"] path = libs/raylib-zig url = git@github.com:Not-Nik/raylib-zig.git +[submodule "libs/raylib"] + path = libs/raylib + url = https://github.com/ryupold/raylib.zig diff --git a/build.zig b/build.zig index 50633e5..8d58041 100644 --- a/build.zig +++ b/build.zig @@ -1,5 +1,6 @@ const std = @import("std"); -const rl = @import("libs/raylib-zig/build.zig"); +// const rl = @import("libs/raylib-zig/build.zig"); +const raylib = @import("libs/raylib/build.zig"); pub fn build(b: *std.Build) !void { const target = b.standardTargetOptions(.{}); @@ -31,9 +32,11 @@ pub fn build(b: *std.Build) !void { const exe = b.addExecutable(.{ .name = "chip8-zig", .root_source_file = .{ .path = "src/main.zig" }, .optimize = optimize, .target = target }); - rl.link(b, exe, target, optimize); - exe.addModule("raylib", rl.getModule(b, "libs/raylib-zig")); - exe.addModule("raylib-math", rl.math.getModule(b, "libs/raylib-zig")); + raylib.addTo(b, exe, target, optimize); + + // rl.link(b, exe, target, optimize); + // exe.addModule("raylib", rl.getModule(b, "libs/raylib-zig")); + // exe.addModule("raylib-math", rl.math.getModule(b, "libs/raylib-zig")); const run_cmd = b.addRunArtifact(exe); const run_step = b.step("run", "Run chip8-zig"); diff --git a/build.zig.zon b/build.zig.zon index fea3cc5..48dd11b 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,5 +1,5 @@ .{ - .name = "zig-raylib", + .name = "chip8-zig", .version = "0.1.0", .dependencies = .{ .raylib = .{ diff --git a/libs/raylib b/libs/raylib new file mode 160000 index 0000000..6424582 --- /dev/null +++ b/libs/raylib @@ -0,0 +1 @@ +Subproject commit 642458242078d4e3299ea31c9a6c415c65a4e907 diff --git a/src/ROMs/br8kout.ch8 b/src/ROMs/br8kout.ch8 new file mode 100644 index 0000000000000000000000000000000000000000..7e675f02405c4d5b99068f0e84de304cf53135d7 GIT binary patch literal 199 zcmWfd|L0G`BBr|s8Qv{oxxElbGAA%4vbHm{30x~I8E2BVjJ#(#fCC1)>rrB;S_3=gv9vP6_vmAI7z8Q%S4w_^~RoX7GHXjiUW mj$F1wmh4GJPln9@8UG<_64)OsRI*Os1d_%|$x4%jwgLe9phWTj literal 0 HcmV?d00001 diff --git a/src/chip.zig b/src/chip.zig index 0b5013b..813eaec 100644 --- a/src/chip.zig +++ b/src/chip.zig @@ -50,13 +50,15 @@ pub fn init(allocator: *const Allocator) !Self { const display_width = 64; const display_height = 32; const memory_size = 4096; + const memory = try allocator.alloc(u8, memory_size); + @memset(memory, 0); var self = Self { .allocator = allocator, .display = try allocator.alloc(bool, display_width * display_height), .display_width = display_width, .display_height = display_height, - .memory = try allocator.alloc(u8, memory_size), + .memory = memory, .stack = [1]u16{0} ** 16, .V = [1]u8{0} ** 16, .I = 0, @@ -388,12 +390,11 @@ pub fn run_instruction(self: *Self, inst: u16) !void { self.V[x] = (self.V[x] >> 1); } else if (inst & 0x000F == 0x7) { // 8xy7 - SUBN Vx, Vy - const Vx = get_inst_x(inst); - const Vy = get_inst_y(inst); - const sub: i16 = self.V[Vy] - self.V[Vx]; - const not_borrow = @intFromBool(sub > 0); - self.V[Vx] = @intCast(sub & 0x00FF); - self.V[0xF] = not_borrow; + const x = get_inst_x(inst); + const y = get_inst_y(inst); + const result = @subWithOverflow(self.V[y], self.V[x]); + self.V[x] = result[0]; + self.V[0xF] = (1 - result[1]); } else if (inst & 0x000F == 0xE) { // 8xyE - Vx SHR 1 const Vx = get_inst_x(inst); diff --git a/src/fonts/generic-mono.otf b/src/fonts/generic-mono.otf new file mode 100644 index 0000000000000000000000000000000000000000..422eed3b5c65e2888c02c402a50d137227e7170f GIT binary patch literal 34180 zcmd752UrwW7ce~Qy0fTZRn|e>)!h|)7kihEC;|fZim(Do3m~YmVDG(aG`6U*ca6RG z2GT_A4UHu+nlt3$o9~>NU0IAKdGo*D^ZjlvJ9FpWb5B3#mf83R2m5m+xD-ywm2vm- zGL0Wpej3NMv*S3=N&Z0%8&BA_^fkvRIQ%y-G&&^C@y79Zj%#&+sbZ2Vk~EA}orWOy8RCjcLDA{IU-B-*_F>BA-n ze;~)TG{?mzB>r)bYs_(N^YFPz8Wx9s^kL260j1l%=afpS=dQ;OaFMF-L8Lp<)_wX)a}#avE>_)Ra?6Yo3A z--`ZRV?}419(1X+y^k9apTN0rCRR*Y>5rz|1!X#yYqGGaSnXmT5P!VIe-#ytKaP*I zTCNpbX|9RoT8S;)EZ4=k^4uWHwTi3Bjj&wvoWQNNT&uY{+&;^-9jE5*S*}aiT$kiJ zaZfGv_GN1-8d`=@Tk7pNO}WyR>k>BCCAo#=DqHI9o&3t}vRrGprp^Cwk44T# zhDRitYK7J|HE!hG)HG;>$=e)bj*kp&U~-L$GD(jUO!4Lfb9|CHtbygBsY`53tjWj6 zxj~~wjoOfW+E|{@10SFIf#&dGQ6cf4G&fnQyPM+^B4cAra`%8BQ=>+7>D(qFHZe3d zCW#(2Xx986fs9Nrg_siKL&D6_A@M^^v4cMnVCnt^986Nj!Lc!kHbmDmg_#o~!(+^0 zHkGEx7>PLzf+Mhx7xf5=9h{gP5^p9TQIVnMm;~%GEGEnxZ%T|Xn}U3NP5yD_7`d^p z+@PMRkZk7$_VyAqOJ{-;8XFf$#llk1s6V!|X0qY`3GAxR;TQ6Yn(%%Z_#=4S`kB1^ z{en%tKJK1=L7uimnChDvH#d2i2gMHyi64PHYShBs{@;&SpHL16^mOgw=IcrPF`L3; zL!uH$ro`1hanw``39W4+IV8rK5)vb$QRfnoOR@1$VabtUW_$ZEa}x41F4~MjLolJS zQOL;H_>jcNB(q5peq4O)5OZi^LOlt_u(-I`_(W+q>1k-Z8QaFj*xQ>255^}FG9jU) zZIPjpkfI`E!iPoT;GqaTI(k@4WMZTlp^*R)D+Hf}n4r)G$D8phCd}SGmQ-VKJo;qB zbSSA;aztckgoICmDLP~XDuF2>0_h5q^&py{;0?Bn3yDue`%H+4jI+0w)FBpm6Q6(z zC9@1!VhV{u9ZHa>_^6#k4pzky>|v;`^^jr1!cgPtnWAIEA_tT1N!a12NKSm@pkawv zMOa6TFomF=$Hs)yHx7sQ$n%)kL{ma+lw>Mc8J%E`N-`%jFa<{-fYn63mjp433OK96pkYe4~dS1BbnRM^pfFe657qpCf?y7>ZnqM-3wHOe&UO zN*ERzK}fjCcClKdC1ltR`-ve%qc0+T4hfUY zEHRd1Sd=*tRRZ;vEN0js^iGMxsK!*kzSW7C@R8(sVll2@wWPnYqqRX?cD?rYpYj3a zGr|-a5fX#k2}iYyj}9T<7*EyYL9Gf|lq00TXpiP(lQ||SGCnqjcr8=xIxI0FHvZ3a zO3@hY&P*ej$pX>T!_jv}({-XbG$JN4G$aarVSFS>6j6|^FbqP$s^FoU`uq%W(lI?5-m=81d7zw zEy`gA9dA?-CyuuG(WfIUj7PTPVq~|tO&oC8Ac}rgs~?Q&9GgtSL!O0UB#Vtnz%;|& z-nq8P&y2~3CBZ3zKDwxQtNKKu7DQrCjCsS*Y6>+85l3zNs7Bc1QyoW}w}w7TP(fhk zsKJtS8rS~ozP9ugW@i?7*631{cT%|fa!8RBjY792rkF!8LX%*~VZ@MFTJ?vzX7AhbLM+VZ^ZLkeK?I zh=fTwAk78IQOCwhEF~|5@gY7oEDrEv2x1v^f!bCSn2-X^sj@ka9>Ag){=e z7=-=}vp3n(7m4$+h_9uSB~D|&i$q_Z6lqSDqbQAznj0?(sR=4c?4RWNWzk?qDV!AX z;`a9B#^r#5fgr(T9v+80j7&6gtld4kQPfM$0IxBc#Qn$cgY*t+q-|1i|k=7 zaR-~AN3i(q7|L%j!jhR-+yn9w;}tT6LL`L{$%DsXH*{E}5ps|u%H_Ce-q^5+r z#mYY&U3w%>5oCX7)0RxC&@w6gL!-8&8aR$MnT(E2L_cAU35$(K?vdezVP29Li8ttX z?Cn3bDr`GE)Ep;Scu45bnAl{@v%+P*fAouJ=7pI2nQ8Ql0xvOVex8|dLku-PK)wJDbuBDYmh<@5=R6;qzir)%Ux{#m|8NAaKkrW$=Q-ZkbX{f882g3F9@$(AAVLZEd`UPW_;)RhrpjV)ecgNs**ee)o>zRTBT|GRzxCVBv zM>zT;)qy6dNdv?aftWnIV>?q&M^|586Fx-)umUl4^!N3^=5C%yfGbW*WrUD;iHY@0 z9puD%Eo^~3MajT?M< zQG#>lV!1e6Xp83}xo|FmOT^{4T3jes8^0TKjW}nnDP9f27ADS{GjlOmsBI|M0Bc;i zDEydgdM8MCW-dW$mxO=Auw~JnCaw#P8iU2e`QRrITZMDO5K;(2a>n+Jq)!_QW(i!I zPas(PQ}6np^e;NrU$yKm;h2Eb5H_YFBL{FnSVMSN@o`2hBCus5(oXG@KK5w9HRGCp zg5jq$ns5~2O$e4mq%s8gV&7?=MjqX(RRea62>Ik&q=Z# z))O|wiEv56VP9xTFo{!E&1k>{BTr$qvAx_P3co{foES+vXpCWa zO(VzSbs|Edwm~RUU;Oq*=~G)1R|wg+2n53Wb221}+2f}Wx1u9k@gX_JAw<$q;*1$J zFcz^UxFHf|)|MgoBmtjMeG-;PNmmEqU&1&U?+JTZx?X4%U8UvP=tafVE)xXzK@}{yx$)gh81Tj(4r3CzrL?0k~zC?>Iksd^$Mv_*MZ4f5( zO}>=$GY)%@U63`)UW4?Q{BQ_zmwJataH+isy$bm%Y8!`7V!0t$8;bV{l9pTX7=};d zq`uVBN?l=J(pa+vF2N(5%m|xW#7oeuxP%~dt8XJ~FSI1G!5GQUlQ$sB=_%nJE%6`* zYp9m|kgWyEILg>3;dA1JY-xiftmHfLBZL+4NA?=@G(_T6vV5zvSgndaX<6c);xS`atr*DVLU9V&BPbt2-&SH59&o6w9TRj zpBQXI)-PugMR{zMyOqa($CpBW7u6-Jrhl$Kag*?%JSZ9QAYR8ynkIADDsP$3gdg#k zv_;k;+jwu~PN7v07e+|-YxO2YwZ0Hzt8BhTNk&SXD)fo6Pbx~2Y#)RV*|==6wzv@Y zKbNCWUKESHh~HM)LJ-3^q-%u42^q4L7HS=eI2B@3=sB$%pm;B9uxtfIF_pDRenOPV znHgCl6G@D; zQI0U=El85o?$1)SwQ195W)vi?jtGf70F71);ZQXEXA-dLBiTRc5=BR{4{Po$^Hugp z2?&$?x>ZwTnaX~hu(0(bvX76&9`V?R?h2_SpF>`Ps3z+ynzLDbrmP*d@|Gn;Ua`=> zJ4y2sTT8ahX#J2P(v~mD!>U=fmY*QmNGM`w>%~bnR{9A;@;Jn8t4xW0IpZxl+oKpk zc$00&d4((=TMGOU4$TdSe-xiS&Gx>8y;Ym68H${HSm#@^Zh4_*$QjC~I%>@hC^MnB zO09@Wk~L97ag4D4G*@IQAI#c`-wN_bu&U4E8N>8l*nX)7r z#p(-UB|l0nNYBaA>51%btuba8YNebzQCzpSp&XmCZ0bRI9@&U&yCi+NmU14lKpJ0e zZN*lWf~}+|!zs#f+cw1cztacVe#lD68fhe1cPLko{WIC5EKQjrOp|A;5?e`|Xrlft>N9PelZnI_7LM{=qPir<+ zn5)aWBfE-E4zN3otx2`}~+fZCAgl>H* z(<;x8zCliVC5EBq;<|=wLBV)FqY?Jbe&*X&7d)yrTFcK^vWwA zvJP9}eVO}m7DZmEsP`c#^jU>F@lwJU5WD z1MA#??9FzjB4b7}8*CHP8%y;6`*FV{KU)1F`>>rW6vgk;xyxVa9qF5_Q~zd`LVEL8 z^AyU($chWCkZ_Up#%ir3Q>#|Us}u6fhd)w zNO+KDSZ9oKhFDZrt@$EhN;+2`zvcNXK_kC49RGfr6GmXvAbG@Kd$M+l;Y61lSIGy; zYaen(OFlfGr_NH@bI>#@!}WveKp z!HUNq2{vU1T~n?SbRd*$(g2gBwKD%w59%(D-&F@U5dgo*Nf(2vd@jT zv7{yzPeQTn@9g#8SdBblA@>6jug~Y=Bq?h)ZaYIE*;2eCI)|goWIZImKv7wa;lyjw zJ~?iEK0+3aiPkL8Y8_+|@%W*!1LDoysbE~G1|0jB_vk+Nh2s3IcbJQB`iPyF>Nvq6l=d4X7sT0Pe zTUMx6uG?mtpPNs9E`KN8D4gri&J^Xxa(?%DxYj5|RQ%cN%Bz2Jq_)odi>|L(>6C4m z;x%ajc}>yQhBPel>q(k;pIA4}!^0`cl+RGmF?`BeR4nphqltEfE2pZ>evr^Iul04aq z>?!}r`UhcS<#pjmwi-#C`qG%TBgnc&+9>-RlFgT5s_aAlo7FYKf+!=eA#=}mY#Q-% zQT(sh*9vvqy3+gCt80V<$%4kGnWjvq?P|-Xc3!AW!5?Qse&{=_>u3Zq_=~ucKOp;} zxJ-UG21l1YhE-xo_>^`mNTXUsGW8{V{)`F5TYqdt;}Uc?{FWs^oTJ@R zTMo&RFVsZB+XKfTjEVL_$=hN~-2YS#gw5x4!Kz&ZjbubT@s&6cggo>_3J8{5L;XpM zNYmXdT$EwTdQP0Rww3uPuYQmeyP`~G-plX^pQ4lyZ9OE;74lx@HsMJ6LStIjRthCe zqY~Cuj7WP)r>r(n$QiOY!i7d6iPIg8;4R6Dbjlw&OHZvfDUTxKV?qjKeUxdVo<+HA zwH#|ZYrn#M{y+N*8J2ZyS%(YhCoLh4%2<-LWsdwkDvutn4ru!|AiQ*>gq?T)>%cxl1csyI7b7HsAjNRS{9@;e-7%Fj=}ZQb6CS;9 zWNK~lFm0{kb6dEb+&1LP4sI8|wYr zV%4uWv05>)x>BrId?HJ(#P9fhrwTTehRm|-DtSgbaHSNA5)Ovi4H~x?vT(~GJm!jr zcJbIWo*I`f!s+Ebd|!hqr6cmgrArTURAPwdu=rRy^ldIYqy2ARf|cJS{i|0{t*^_P zKL1)QISGZCi>S)YQgRE5qcU(S6x=E$w@%4zD$ku%qL8=?<+!^_?h&V$!gF_!f%pox z5?4lha_1DGiZ{iU7du$&S7m+W*UAOTZOW6%Eafl7`xT#9{Fcg9HAz zT0LLAU!9@;-OgKl1U}AOa4)+N~xey6H2Wt_4cc}Uyb|ftbHr{ z8TM~A8jYuBgyu%+(xqFM9$0!z=_RH2mA+H@O&R+#bIZIa+pz3}vN;Yl92PlT))v!t z(WYouX$#7=E;p{+6QP4JQpj_xTl@_Dj6$ns$8OS<;t#=lPb@ue5~@l%J~K-Ll?t%!!BbV;|1dbV}Yrr z$!r>7+F&|rdRV1WmHt&GRoPMHW|b#Sb(~gKtywj$>aJ?vRAbe**U;5)tIoIk^Yi{l4wP)1P)G^fwsF#D7o4skiq(z+;&suJ1<=pDq*4oxH zTEA~oqfL05jcqd9I<}2#o7S#%ySeSlv>(v^Xa`+~!5wm3bS_>l!(9I3TE=yeTeL@l~rLYP5s6fZZ zus%4DbYq{!l+t4;mBu)JJ-Dgd_;}_u>t!0bHUOZ_zyvU?=uPe^vS{S1nw zcDe|fhprE``(aOhOM%T&yDSOamZM8Qus3y~;by^GLHpw*SjuOOIUU1Dw`KO=Py?k{K#B|1_#={eSIwFnL&HlC9hX@&00O% zHZ(vjPzV>6`1kHJwu@hvl#54>Ek1M62)jOn2yYJOJj?5yUDK#NbfQ-8HjNq{Zu`bK z{i5JE?!w_C3(sGadX4VlZ`35@cD>apDSzlChy73E9)kaaO_|yy&@EpnoZYkY%$aTG zpq4$t{rVfiPn&n8>2|U;!n`@qoF&ekIA`3vNfif$_Z`(;ukF($=;Y(9T|3V0HNrFY zn{e267*KWS#hQS zrC^zgRpYy{WhxER{a#N21D-0y8q871!4#6)P>Se&d`8o<(Y#ItH(O& zn9p_A032CagC_q6g!<~S62_yRH{e0TG`CoD=7w$Bv%0J_D`&{#p*(1>KyB!(10Qc_ zz#M^k&P!HY73V17rr2AU#e;%*=bmK-Xrh{}hQ22ve`5ZPrp{aq;i?+^Y8F0qItzF@ zq%QRBu9~W52G%6Et4aemvlXxS9ycp*Al58?Z9;I`0qAPybQ%mY{q zYh#4Da6x#pqfv|HMs|t$tEPQBbLF?X-W{sFWVuY^B zH>^LaVbILT6o;llIb`>qr^@$^V!uq*CEwYFZ!#dXsiC7MbSfCiI`NvBx5ek_ibr{{ z|Dh6m#PPx}d+q?AeJ;3NJmHQS zp;Q$&nLTUViYZtf!`$WPrmoU!MrOh0Y*>=zAohRe@kpBo-@+5&GXL;$X#K`s!OiRW z9e**-*o8d3LQ}w8|4+uuc!uns3rzU+(BkaLaK3?jG!Vt~~;cp{LN4XQma8-NC4b zhCjc8Mo_)^TUN)Y%?%13F>tVP8ova_s^E4`K^=Zrff2*0_Q>Ha$5)-!zdPBsb~V2a zCg&bU9*s3>Mx?`&+b|$YarFsFTRbD)62LCG=}A^x&nASkaV&s&9A<674E0~UhdNNH z#Vcm&;5yXHY@EtNnc0h>Bec?AJ08~&C10apjZiZ%^TnH!SKq!m?OwlraQm7@BtO6Q zZAF?mMJW!>uPv~vA8M;!@Q|6`UUjZuN*lFi_;uKO4c4VOTz=v940b;Ac&2^%Ks+jR zIMg8{-Y`wAecL>-t9xS|s|qR~w7QC*I}P=pbW3fr#(;8onYI34Y7WI7{xZtAG%M!I z^adD<#&h>3t>5+Pv%KT!uZ+y8V1a-NVYl*uH`wW6!g`nr zU7$lSv||03Q}tS`D$_r$2bI!pe1CbTaSqQ^<5@4((6#6Gj1j2oXn0EsZBeXjjhF@N zihqoqm8rp6GS;<)4SYX1*&6vxah2kh7@@ocrvx#AjZ{5UGX`#e1uqMzMgt?V-eoDC zhy#^R#F0V=abT9ZU?5v0;6=eeUPB=A5QKPE5411EKH@{6b#Qo4Z$t8kxDjI#?8YWdk4w;XzN20@<=Eo2h6O8DEM2Q=3@qZ;sM4y`_gcH1cf&|Nw=gW{|uOA29a z(XIN;VM}1Hfedwcwy1ur*dkt2!c|m(1^nWWc_B+euCr+sSSWuTrY+gGWbgd_6`@=~ zuyC0TSH0ocu>5%{rr}w_hk2^!{3RH!(#%MOgBdV4!{PB0IQY`#wf5Z;M;OjSJ+Fg5zN9^SJr7e3y0*K>)X?I zq1|amHOv(q*>@eFldoEPq*_2zR*bbB3%#H$2#cTxxJ{F!{W=Hok`j7c{KVQupXErHNjvF|k z|C9k0C-74ajz5rcpoz#;Ku1+f+y})J8FwM)t};U!@os+Ax$5~tmknJS4KoVPR)Es^ z^ICqWI*A{*dEAyUKd>3%xeA(ZGV+CN#nCtL;+67I%rwF}mLagF(^Fsol+Z!R`7jV#K-)R2H`6ftNthzEnQ73#!EDINRQ!|& z$6qN?TMr6QmQ1Ns4(erdrn<@M8d=hZd6<%w6_o-j3t4C>Q0ziqredm2J(D zDZf{y!?9QJ`X}u{anwO!t~&3=B53$t2VKm7K9h|YNLaCeJuDPP8MMQmgzl(dW#&U4Xg%Ga zS(UUl39@d%-YkcQd2l)pRrrorUBG;IFVyCrobO-1mS2y$#wqOuXw0%z?|9Y@o~UN2 zfBoTfCU82a?R1qH4Qv{!GZF{c7%4OJ%jG!Sd2W zU=wuJ+lMx+;dAQe(T4gwYp@VXw1*mcs0pPmBfGk@mh)MSZAQ0m2X4sKJ-xZ>&f^RH z8!a+ufBuz+TBB=SV@{HKy5%U~`#hxx`EEi^!R@v%?Sorhz8l(QR(^1@;>J_B`BZrW zHKPC(sZCZ@a0j=nU!k=@oZqNmo|VD8pyMt3`+A zAD(xp;(UHypZR?j_043jDuBQrL5MAQ^M^*N@%+>SW6zH}&<khduVD>%KX%DC^Tsi&i$pwZV=O3DVXqMerb;BMlY6OR@J#T=8-GzA#j*q*w z$WA+IR+=h2Vafp`j8Q}R_7|8#jkX0-&w7d*epej`F3Z+?dch3l)NaJ+K=b`b3WPvHVKYC+4t$uzv{U@;BP`7%`uUTsguj zcW+;BehVcopNa3V7*p!3kR&lG)#3D0IR8{TT;hQS1(NyqlitW4A+;9hTi1$J-Gcc^8!$F>RL;$|(Us;>?4+vKKaP25;nR)d)? zuyWv>n!4-aHsj28JS*OF5L0w>Uvznl0cShKmw;X5OFarzeGV{R|1L=ky#+Y=|LrME zML!kb_*7K#z;{2#?7X}NcaAYk2eHwIoloHg|Lnx6XD?@U?jX%J%iMeoItN$)x@r%! zKf{9jnbsLGJNjX!Aby?6Yx7tyu_v434y_8xuUkY>P7kyP;d(xA z&0ahGr<j?h&7>@~QuC92~U*m$-=C9ZDvVG|`F`NB;F?0l?z=7{LAp7~*H zxjVRisKkPK%?b=M>ypH<)QQOs=N>|Zhfw~Zw&6K(olvmuA!Mje&Uu<~az>v)25pam zGE{M5e%{R!(|d*CItx~aRn@nWFFN}VjtLxLn983?F>eagcMXjU9%Y=8Mb1tP%^s8N zfLxybRGTdZz%7B@LRPC~R(rdI`NkM9LLGeEykH$NTdmC|cE6b8+uooVb6sqFLy`Uh z&OKE^#ux!yZlXVL3e8hZufJ~m z1xgz=taTE&rHT!1rX)K&djc08X<~T-cmgYD zsRP#M4n42`;fHxkHX4EZaSIg3$cp`I_~W8Ziw?enJB1mhJwg$;NdnI_vHsbvHyutt zf%ADd*V)ekg!}=G+u#f|_exCXO=#>bU6~`R!)ji{B%@{8h0Pat7-zH9JadNWf@Ivk z?)Vw(e?974-x#E>>*re2K$_a~0b&k~o@j4~{$ej-|L&BOy~g|M`@DTVwwP{g$89 zT{^qs_{q~_{1zK#&CQy=Sg%RR5*;zoo%uw0R$ME%s3CBsik(%n64wVov3)xhF5P3u zRF4fGJl?GDGGyIlBOH?)1vGcYrLiEjW=y*Oc`+Ob~Mu&_?HN)B_GV*O^%*EbZRt$nzmuu?#bV~!@CMic~`8DpB6fehM2Z_ z`qs%m1j4TsWNTGi7pEyh7q!7msX`rQR{hH3^lB6M!W;_!Iu<~gLPcEW z!tjr7V?a{=fMkaZah7&x1`#$GarBNG7u-z$b=>Uv@kVW&i(0#L{DuwFHt9jV|N5(4 zMu-;As2cIC40}2*c3kX)n2J;Q2^+?3M5-%59#WklmPuDU6?-dxLx^sWqh>D(N~zk4 zy))Q*HM|l_scxY=u8<19X2M&{D>P7M);%SD?Gu(SpSePRcFi|9ZEmZMogK3<&QO0V z*w4D7PgVDGo^~wRIBCT+T)2bQ>PsUIdM`H2QD0xY^57+^_;%LHc`FPjRzI3{NS``P z&2$Ziu|`JLO5HGUxfd>c;&LKv$DC@rl)_~GUI&p7QsM0__!W^je{U9)Rf@|+l`wkD z^wIh*35%~8p{*Jk)L+9Y7>0TBKR%h&^E>0bZ)S}csbj6xzAO7(PcckUd#8-(=SvkM zr;V8OjiE;gd$tAx0kl$ITD|)gF?~#$!ixY)3 ze$&jQTej-8JCCIKr|NU|U(7Ii@Nv^q!iVU!asAqz_13rP@81D^Oq-;9CkhNJXflpl z9AX)?{xP_@;l_`~jSlU7D1n5bgmJdcYZfJmKPEf8egvuud5_xO)xHxCfQ!(ApXeVs zz;B$L=!|pw6XUmr%Je}6>Ta+3`NGxf*|S}pn;V|-P_@Nv=ETa^b!HWN zFYC9wuc3{{&%E~Kg6IW9qjavpgL@1#c&Rm^`4<$E^9@SzeEw>o;L3*!syn=RCI5m7 ztpJ+d$h)C{&8SA3p()PT?x7bCy1|0-pz96hrG~-qmJ<4QY(J&H&0xQ(IuBv&t%`N@ zg6<7iuos$xOOkjr(=!t`WS~2|ATHDHg|*1!O!24+MrN`&ejRwIZYk;16`my?^%=}|4Wky0n*YswR_Sbo{;LwdKc;Ik zMyPjoU~Z%V9J;Htc?U*p9ob#iDQHx;4iVckQpk&zo}U{8CH3!K?gIPW<2J=^G|on} zuB_j6I8FB~V0Zhi23Gm3s{g9^mB)2!7Jj#Ug<M74S=%=a;gKTS`@l1iW8mU`TIIV;qV7UN(&|k$A zJnPN|;xuu93OBptCZ5#f4_JcJdDfq*ahf}jb;n&BY_9n*c2=fX3e!k;%B=cZQb}=- zlwh4r`9el{It5e8vniIGa$U-2lS=KylzjWoC1^jm$Qc^1UFKrR($s~BK-Zh2rF>Sr z{#bkM^9W?A<)LlI)$ae8&We$iRI8tyY6+BT!9LuVF+qV6Oky#iJrv-KT=dJLEEKlf zRl)io*7pziiz$FU?X2H0^X1Kg3H5({@jcbVu?bMnLl)vU; zC!1Xxv8**mMO&oFE9}V0%~6W0P%qkkn3uz|3AnCMP(3Gqo~xRbY7vO3zUDz!;HMZh zxIR5ufeRAja7zjTgd2yCUr+0E*u%S5w~n3%d)`FPC+#IkTWCo|_mbo%B~Sz5Mv*6M z9$RL+9&Mwlx=(iD*jJ9$y(wu^PSUxuVjU%vJ0m@f4WPLdCOSKI5?Z;=zkkFYmvy z^U|-Ewd}-d5d^o@&QG7OyMHS?@m>Sx_@=G~;GKj%Q`5F>o1J?2$Q-YTh$)`EjhLEt z61>#n5Ll_YpCSVNLTqBU|hEeBE6Mj#2yJ1i>QMMp%31Gw{|p{%H&i z>zgg!!Km}>VK#-GJD|9axvBReA@|gd(?<-ezTNoUlC^eAR?pn9PItMLI$=)FltjaX zZ@w8bCdICmN0h#Q%^f*h`|(?-ZdlZOlDb+yccy3?eB?GRu|pUx*IQ$j_#V6! zxC+-p3Ae=eLN@OY-YTZY{d(!aGuFj~c(&Q_1hTU=iQwBt(Yva&F{tzFt8`@X=l`rDh* zZp<>e@n27y6hYzF9CA89-zL`0YnoA$U*@g?V@jG5n&yWJEg|hLu3;3v=?*O&pk)>` zC6JR5xpsW5W1ZgqdhGHWgBzyIz2ZZHgA(=}!~BD%jg$GbaWcQqWl*)CfEwWV00bq} z$e$wM%-RtI^#djdfFC9x19FZ*t`fvj%8(bJK@ryd-o0o z)EglrVe6hf>$dLOzi!B&kc6QF(0HQLc3)G7id5t&ix$ov-*ow0`1&B@P=3!tRS({i zHBhDTi{>w$vqZmk$=Jliv11d*jGeP|lCeGCeeu5GM{y5ZVt!M&qZI9tzCdJb2LFO2 zv)-}r2G?m)!I-3gMQHZb9Y?NL1todJc<9`(CZ9K~Oy#2@=k40E`}_5{hA#s|fhnOi z0{jI5{>uM?0NeAfRaMh@*I#jS2tC=Nd``KD{1|Tb;v_Q{YGxjg*6raY?y=s?cM%H8 zvMOq@7t5+x%m)`C9XB>`qiZ-^%Vo*9oFABj+tQ06KDVGe&t}qs9h|-tcnvC@hmw~Z zpyUJSpM}dqn{kZ(F{=+ffvTg<%{Go#cj)QY4%xX-n1>6S0SmTIJg#4VZ7d%>VcupV zxZ%XI>v2}RdCRc+6AiccV+(ezI;_9-ZEr7QwaKjW5>^Y>gc_db_( zmcuXMXIvUi#|?s02S)bUWMq2^JXNF5OuKMS2bu-Y7Z)Z!I2I4=x8_)^5%!4Qs^$K( zJ9p7JPh_o}dmF|i#7&OV!#c1UPziW5`l`-!NYN0yZCty`a++>hehMgDGIMBw} zgz4}!%8A>mo`LHB6sB#ld2%x|AGdO_I z`4yfKsMxU@P7Z|2wZUMVbw#L?oRgcq=3d^*@7lC(pWLFpQ8Vl|_}=Jr78<8}-*A}o zs`V{!&TEznUb)(X|!cA6&lG?uVu8mTb}ApV8FU$bMmqg`iOf4sThq z!&?cNiMRvnzRcF|&-97d@9#x!ng5M6Hxt@(bF5+Zx)M~Dn#}8cVudmTPpw?vLE&k}-@3x=PKOS4Ri!rprw9(;4 zZH7z7xFOv=bd3*rzewM+?eY(X*}V6e|+=_J+!!k zr$dmF-=zDbg6}nGoa%keVdWEOeH%RPYIi`-GeTt2${)rUN9>)Jc|dnz8q|2QedX|> ziwsLcW_bXBw0+x|wMNYpJm(~q%7(>h4p8jLZ%|^*lU`4>V{k|3qEK-0iFihRck{Kpoc(>< zbub6EHYB%S!)uSeJRe-UV@wa9Bx6eCkXaG>_B+*3FbmZ&$&1`S*gL3Q~E03v68t#ro z<|@TV+>+0QwFLv)ilx!(`rZKFv_3~1-kyfC$D#LWbQTlx2MR}6RPWpDhtu$b8pKR0N`;Y^-d_Rz1qV2s{d-F&?sN~db{Mn;DtZN&gy*Lm(^8atJ{uy-lvN21so*@!NYc|S` zvM3o_ygF=M4J}*>$QR2CVs)2->gs}J#WTfDE|^2Vx&&L$xv8>Y{w*b_#KQtBdA|ud z$*=C;hp(Vy(}$?hC7YsN8(AD8xh{Mr~brhAuPU&+f}Z~cupWPo*K5H|>i`Ll--I{WvE4)zV%a%qCG4Njou=JxnmpZD_M`?Nv(yqA$Nd^o#i z$F?iFpPKA#QM*Nuli@ahdDhNN7xhOE#0Lk5MD;NCLqhMRidC;TKpCsDWkbdM)}Pa@ zyuZ_}Y^!c*VC!D+-X}BFuOL70Q2hXXHWv{?lfXN<6AaiYU3OjE`+4-=hv(r7au!(T)Z)aRTX~O zqCQ?-b&d~&Vyf-)zTLc4cVytY0R|IWfOMfT%cn}J2yMlIIcVM*v76$#*j@P?yo6K1 z&<@%|JJ#X!Y1SUEIzZdtQ@AhU?lcbj+3-=T*x z1}J8E^2}Q-_{zp=V{svr=_x z&trC=-MVSfx;4gi-!D13edhGpGj+7sBAr3ORJ7iWc_R7(cz~lBh_}qj`xwmmzlPQi@!WHqXu$W(0byKyC)$Eune+v36+OcHk zyd4!mSx`ne!F*^zBp|sa(!@PBvmvVhwj76ArxeiTJf2G`JPh=2 z&@Uri6>R4cG`BE9zv9+ugmqeB{7BMsX)5I;O{HG{Yq~t}4Bf{m*l(Ykg1xq#g0a}k zyE8W6iJ~;c$u|&wlQwN`vsl|n&5{kOJv(Qw*lGM}-+{aZZ|sVkCJMkE2HVp_e?E)U zWHajrU56U`*ZR9zk@XM}S;1eNBVsYo!&brnVK=XfU(npb{({51yckJ!wZDJq6ch7? zAgf+#f3wldLNI@gz1u~hCs~l5e}@(%pEM$h|KhL_3;Df75sK+_(%_aoN~hRdLF;4tmN7f`wuW+-iSgGQsomyS8pZUa>%$lH5=Cq2H({_ z|K~@Iw3*v33hQSr*ovzQFE$_Rc1oYU!C8Rk?y1Ue3;2y2ehMosub;P5zDBRVwJI;$!kS$4-mV{P!z7@cIy z9|7oz>Vf0r2XoOAW#hKQ9)az70Q=N3ETPlJTE~?7srw)s8pRTGNkXKa?a&`_%~?o`71OZqd0P343^ZT>pg8L4ve2&Kh^F{Vcz)C)IT`{mYDKy(A=By zS9sbAd8_Tp_OkzH5`l)T zr>}2J>zgFTr#jq!37s>w$#A#ms{GQ8Dk$~*0+gl$eBNUN`%4G--v44bz!&bb*odcy z?T0tBIrwMP#?=q9-ifIwB1+Hz8sHl{QM72 z@D&|u4qQlnsx2pu5Z@Kqr;+>#d6O5H*@6G%un*BgO;sNbkRAN7N(Vr~(` zDFjERBcC1I|0RG)p8>c-tX)LR+U>Gc?I)|UK*!x|G%W6MU&&QQJ@WZOgQM`!;F164(BODn<^2r)h3^PL z3cfjh9==k+{45s5pLnN&7Fnz;PgSjPQ{^iao8JMV`0h?BJT;52-%yoiMz-QEzC(By za_Ke1p7a{xd#DCjwTqvKIw76kICII?ZTe-)Cr?>w)NcRgX!ej}`rO?Y zGVxqN?DWwQk@_*C=gt{x)OKIl(`{>aed|GZx)1DGDdF$C#MpGP4usPZ#X<3q@}Q%5 zh-Vw~!&I8#InXuDJMD2AbkA@AtLl)HhUeU#E#3AD9?N~O+qJ_2BOZ4tGp^kmRsl~JcfvEwHIj@q zJ)ZO0yHk^A^qie+*U;k?&uV^$ueRcgwov(29tg|vExX;jw?%?`^=(_XZvl7aw0Y4s zhogH^_pILwb@n{jqrH`V)KT2JyI?E6`c+;?y{qcY`@5;q)ml?ZD0APF&0lJ-I?h9< zZ34634JEhXim0~dYCH?N9ap;Dp}Cs*iw;6)N6%~6oY$J{_GF~G?%}l^%ZQFcg;1vA zwN7^RK&6U6XrSU>FqLXM&yK@7Va>VK0jjB>6b9q@2Y1yze(6?~R&nAgzjO%RKp?ma zp}e+u1*TH}34kW~J8}II7`7NsudPsLmV8<&zg)RW>YoJsU6x`gAIk^4o{gmmmgZP0 zVX2GdA(qQn=3#Ne(i=-QmMvJQPfaYYSR%0mWAVY#0*f~mYER!TSf~xPzmCNZ3&H7! zr8AbcSWH-YVWF|9j^K^P($sPvhTlW548TJ5)TR>_XDkD;5Z*noG{i!dbIWn{4wg$}>xV8O8!YWxnxf^G08j$}CP@Eduju*2^-EYVn2 zW7(zRIBzO=JrfJzOyvWXZdlG^d5C2g7V1ZQqVcs@2uI@mdn_BUP=A8C9?O5Q%*S#F z%Uvv3uTbDOwJV8u<>Pk}mank1#gdGrI2Pis0~W%M+LBDmt7VwIt=|@UXhkh85KAmv9ApDV>-uM&8Wx2-x2ZBiEJOBUy literal 0 HcmV?d00001 diff --git a/src/main.zig b/src/main.zig index 879c563..9d09f28 100755 --- a/src/main.zig +++ b/src/main.zig @@ -1,11 +1,17 @@ const rl = @import("raylib"); const std = @import("std"); +const print = std.debug.print; +const Allocator = std.mem.Allocator; const ChipContext = @import("chip.zig"); const RaylibChip = @import("raylib-chip.zig"); const assert = std.debug.assert; +const Tab = enum { + MemoryView +}; + pub fn gen_sin_wave(wave: *rl.Wave, frequency: f32) void { assert(wave.sampleSize == 16); // Only 16 bits are supported @@ -25,6 +31,574 @@ fn megabytes(amount: usize) usize { return amount * 1024 * 1024; } +fn nibble_to_char(nibble: u4) u8 { + if (0 <= nibble and nibble <= 9) { + return '0' + @as(u8, nibble); + } else { + return 'A' + @as(u8, nibble - 10); + } +} + +fn hex_to_strz(str: [:0]u8, number: u32) void { + var i: i32 = @intCast(str.len-1); + var leftover = number; + while (leftover > 0 and i >= 0): (leftover >>= 4) { + const nibble: u4 = @intCast(leftover & 0b1111); + str[@intCast(i)] = nibble_to_char(nibble); + i -= 1; + } + + while (i >= 0): (i -= 1) { + str[@intCast(i)] = '0'; + } +} + +fn is_point_inside(px: f32, py: f32, x: f32, y: f32, width: f32, height: f32) bool { + return (x <= px and px < x+width) and (y <= py and y < y+height); +} + +fn is_point_inside_rect(px: f32, py: f32, rect: rl.Rectangle) bool { + return is_point_inside(px, py, rect.x, rect.y, rect.width, rect.height); +} + +fn clamp(value: f32, min: f32, max: f32) f32 { + return @min(@max(value, min), max); +} + +const UI = struct { + const TransformFrame = struct { + ox: f32 = 0, + oy: f32 = 0, + sx: f32 = 1, + sy: f32 = 1, + }; + + frames: [16]TransformFrame, + top_frame: u32, + + mouse: rl.Vector2, + mouse_delta: rl.Vector2, + + pub fn init() UI { + return UI{ + .frames = [1]TransformFrame{ TransformFrame{} } ** 16, + .top_frame = 0, + .mouse = rl.Vector2.zero(), + .mouse_delta = rl.Vector2.zero(), + }; + } + + pub fn pushTransform(self: *UI) void { + assert(self.top_frame < self.frames.len-1); + + rl.rlPushMatrix(); + self.top_frame += 1; + self.frames[self.top_frame] = self.frames[self.top_frame - 1]; + } + + pub fn pushTransformT(self: *UI, transform: TransformFrame) void { + self.pushTransform(); + self.translate(transform.ox, transform.oy); + self.scale(transform.sx, transform.sy); + } + + pub fn translate(self: *UI, x: f32, y: f32) void { + const top_frame = &self.frames[self.top_frame]; + top_frame.ox += x * top_frame.sx; + top_frame.oy += y * top_frame.sy; + rl.rlTranslatef(x, y, 0); + } + + pub fn scale(self: *UI, x: f32, y: f32) void { + const top_frame = &self.frames[self.top_frame]; + top_frame.sx *= x; + top_frame.sy *= y; + rl.rlScalef(x, y, 0); + } + + pub fn popTransform(self: *UI) void { + rl.rlPopMatrix(); + self.top_frame -= 1; + } + + pub fn update(self: *UI) void { + assert(self.top_frame == 0); // Check if 'pushTransform()' and 'popTransform()' are paired + + self.frames[0] = .{}; + self.mouse = rl.GetMousePosition(); + self.mouse_delta = rl.GetMouseDelta(); + } + + /// Screen space -> UI space + pub fn apply_transform(self: *UI, vec2: rl.Vector2) rl.Vector2 { + const top_frame = &self.frames[self.top_frame]; + return rl.Vector2{ + .x = (vec2.x - top_frame.ox) * top_frame.sx, + .y = (vec2.y - top_frame.oy) * top_frame.sy + }; + } + + pub fn apply_scale(self: *UI, vec2: rl.Vector2) rl.Vector2 { + const top_frame = &self.frames[self.top_frame]; + return rl.Vector2{ + .x = vec2.x * top_frame.sx, + .y = vec2.y * top_frame.sy, + }; + } + + pub fn get_mouse(self: *UI) rl.Vector2 { + return self.apply_transform(self.mouse); + } + + pub fn get_mouse_delta(self: *UI) rl.Vector2 { + return self.apply_scale(self.mouse_delta); + } + + pub fn is_mouse_inside(self: *UI, x: f32, y: f32, width: f32, height: f32) bool { + const mouse = self.get_mouse(); + return (x <= mouse.x and mouse.x < x+width) and (y <= mouse.y and mouse.y < y+height); + } + + pub fn is_mouse_inside_rect(self: *UI, rect: rl.Rectangle) bool { + return self.is_mouse_inside(rect.x, rect.y, rect.width, rect.height); + } + + pub fn is_mouse_down() bool { + return rl.IsMouseButtonDown(rl.MouseButton.MOUSE_BUTTON_LEFT); + } + + pub fn is_mouse_up() bool { + return rl.IsMouseButtonUp(rl.MouseButton.MOUSE_BUTTON_LEFT); + } + + pub fn was_secondary_mouse_pressed() bool { + return rl.IsMouseButtonPressed(rl.MouseButton.MOUSE_BUTTON_RIGHT); + } + + pub fn is_holding_mouse(self: *UI, rect: rl.Rectangle, state: *bool) bool { + if (!state.* and UI.is_mouse_down() and self.is_mouse_inside_rect(rect)) { + state.* = true; + return true; + } + if (UI.is_mouse_up()) { + state.* = false; + return true; + } + return false; + } +}; + +const UIBox = struct { + x: f32, + y: f32, + width: f32, + height: f32, + + vert_margin: f32, + horz_margin: f32, + + pub fn init(x: f32, y: f32, width: f32, height: f32) UIBox { + return UIBox { + .x = x, + .y = y, + .width = width, + .height = height, + .vert_margin = 0, + .horz_margin = 0, + }; + } + + pub fn init_rect(rectangle: rl.Rectangle) UIBox { + return UIBox.init(rectangle.x, rectangle.y, rectangle.width, rectangle.height); + } + + pub fn body_x(self: *UIBox) f32 { + return self.x + self.horz_margin; + } + + pub fn body_y(self: *UIBox) f32 { + return self.y + self.vert_margin; + } + + pub fn body_width(self: *UIBox) f32 { + return self.width - 2*self.horz_margin; + } + + pub fn body_height(self: *UIBox) f32 { + return self.height - 2*self.vert_margin; + } + + pub fn body_rect(self: *UIBox) rl.Rectangle { + return rl.Rectangle{ + .x = self.body_x(), + .y = self.body_y(), + .width = self.body_width(), + .height = self.body_height() + }; + } + + pub fn rect(self: *UIBox) rl.Rectangle { + return rl.Rectangle{ + .x = self.x, + .y = self.y, + .width = self.width, + .height = self.height + }; + } +}; + +const HorizontalLayout = struct { + x: f32, + y: f32, + width: f32, + height: f32, + + used_width: f32, + + pub fn init(x: f32, y: f32, width: f32, height: f32) HorizontalLayout { + return HorizontalLayout{ + .x = x, + .y = y, + .width = width, + .height = height, + .used_width = 0 + }; + } + + pub fn next_x(self: *HorizontalLayout) f32 { + return self.x + self.used_width; + } + + pub fn next_y(self: *HorizontalLayout) f32 { + return self.y; + } + + pub fn push_rect(self: *HorizontalLayout, width: f32) rl.Rectangle { + const rect = rl.Rectangle{ + .x = self.next_x(), + .y = self.next_y(), + .width = width, + .height = self.height + }; + self.push(width); + return rect; + } + + pub fn push(self: *HorizontalLayout, width: f32) void { + self.used_width += width; + } + + pub fn used_size(self: *HorizontalLayout) rl.Vector2 { + return rl.Vector2{ .x = self.used_width, .y = self.height }; + } +}; + +const Range = struct { start: u32 = 0, end: u32 = 0 }; + +const MemoryView = struct { + const Highlight = struct { + range: Range, + color: rl.Color + }; + + font: *const rl.Font, + font_size: f32, + + base_address: u32, + memory: []u8, + scroll: f32 = 0, + + scrolling: bool = false, + selecting: bool = false, + editing: bool = false, + + editing_byte: u32 = 0, + selection_pivot: u32 = 0, + allocator: *const Allocator, + + grey_out_zeros: bool = true, + + text_color: rl.Color = rl.BLACK, + dim_text_color: rl.Color = rl.GRAY, + + row_width: u5 = 16, + + pub fn init(memory: []u8, font: *const rl.Font, font_size: f32, allocator: *const Allocator) MemoryView { + return MemoryView{ + .allocator = allocator, + .base_address = 0, + .memory = memory, + .font = font, + .font_size = font_size, + }; + } + + pub fn get_memory_row_count(self: *const MemoryView) f32 { + return @ceil(@as(f32, @floatFromInt(self.memory.len)) / @as(f32, @floatFromInt(self.row_width))); + } + + pub fn get_visible_row_count(self: *const MemoryView, height: f32) f32 { + return @min(height / self.font_size, self.get_memory_row_count()); + } + + pub fn get_max_scroll(self: *const MemoryView, height: f32) f32 { + return @max(self.get_memory_row_count() - self.get_visible_row_count(height), 0); + } + + pub fn show(self: *MemoryView, ui: *UI, x: f32, y: f32, width: f32, height: f32, selection: *Range) !void { + if (ui.is_mouse_inside(x, y, width, height)) { + self.scroll -= rl.GetMouseWheelMove(); + } + + self.scroll = clamp(self.scroll, 0, self.get_max_scroll(height)); + + const from_row: u32 = @intFromFloat(@floor(self.scroll)); + const to_row: u32 = @intFromFloat(@ceil(self.scroll + self.get_visible_row_count(height))); + + const scroll_offset = @rem(self.scroll, 1) * self.font_size; + var layout = HorizontalLayout.init(x, y - scroll_offset, width, height); + + layout.push(try self.show_address_column(ui, layout.next_x(), layout.next_y(), from_row, to_row)); + layout.push(self.show_hex_column(ui, layout.next_x(), layout.next_y(), selection, from_row, to_row)); + layout.push(self.show_ascii_column(ui, layout.next_x(), layout.next_y(), selection, from_row, to_row)); + layout.push(self.show_scrollbar(ui, layout.next_x(), y, height)); + } + + pub fn show_address_column(self: *MemoryView, ui: *UI, x: f32, y: f32, from_row: u32, to_row: u32) !f32 { + const font = self.font.*; + const font_size = self.font_size; + const margin = font_size/2; + + const memory_size_f32: f32 = @floatFromInt(self.memory.len); + const memory_size_log10: u32 = @intFromFloat(@floor(@log2(memory_size_f32)/2)); + const address_column_max_chars: u32 = memory_size_log10 + 1; + const row_count = (to_row - from_row); + + var label_buf = try self.allocator.allocSentinel(u8, address_column_max_chars, 0); + defer self.allocator.free(label_buf); + + hex_to_strz(label_buf, 0); + const column_width = rl.MeasureTextEx(font, label_buf, font_size, 0).x + 2*margin; + + ui.pushTransformT(.{ .ox = x, .oy = y }); + for (0..row_count) |i| { + const row = from_row + @as(u32, @intCast(i)); + const row_address = self.base_address + row*self.row_width; + hex_to_strz(label_buf, row_address); + + const cell_rect = rl.Rectangle { + .x = 0, + .y = self.font_size * @as(f32, @floatFromInt(i)), + .height = self.font_size, + .width = column_width + }; + if (ui.is_mouse_inside_rect(cell_rect)) { + rl.DrawRectangleRec(cell_rect, rl.RED); + } + + const text_pos = rl.Vector2{ .x = cell_rect.x + margin, .y = cell_rect.y }; + rl.DrawTextEx(font, label_buf, text_pos, font_size, 0, self.text_color); + } + ui.popTransform(); + + return column_width; + } + + fn is_in_range(x: u32, from: u32, to: u32) bool { + return from <= x and x < to; + } + + pub fn show_hex_column(self: *MemoryView, ui: *UI, x: f32, y: f32, selection: *Range, from_row: u32, to_row: u32) f32 { + assert(self.row_width <= 16); + + const font = self.font.*; + const font_size = self.font_size; + const margin = font_size/6; + + var middle_margin: f32 = 0.0; + if (self.row_width == 16) { + middle_margin = font_size/2; + } + + const cell_width = rl.MeasureTextEx(font, "00", font_size, 0).x; + const row_count = (to_row - from_row); + + const highlights = [_]Highlight{ + Highlight{ .range = .{ .start = 16, .end = 17, }, .color = rl.RED }, + Highlight{ .range = selection.*, .color = rl.RED }, + }; + + ui.pushTransformT(.{ .ox = x, .oy = y }); + var cell_bufz = [_:0]u8{0} ** 2; + for (0..row_count) |i| { + const row = from_row + @as(u32, @intCast(i)); + const row_memory_idx: u32 = @intCast(row*self.row_width); + + var cell_rects: [16]rl.Rectangle = undefined; + const to_column = @min(self.memory.len - row*self.row_width, self.row_width); + for (0..to_column) |column| { + var cell_rect = rl.Rectangle { + .x = (cell_width + 2*margin) * @as(f32, @floatFromInt(column)), + .y = 0, + .width = cell_width + 2*margin, + .height = self.font_size + }; + + if (column >= self.row_width/2) { + cell_rect.x += middle_margin; + } + + cell_rects[column] = cell_rect; + } + + for (highlights) |highlight| { + const highlight_start = highlight.range.start; + const highlight_end = highlight.range.end; + if (row_memory_idx >= highlight_end) continue; + if (row_memory_idx+self.row_width <= highlight_start) continue; + + var highlight_from_column: u32 = undefined; + if (row_memory_idx > highlight_start) { + highlight_from_column = 0; + } else { + highlight_from_column = @mod(highlight_start, self.row_width); + } + + var highlight_to_column: u32 = undefined; + if (row_memory_idx+self.row_width <= highlight_end) { + highlight_to_column = self.row_width-1; + } else { + highlight_to_column = @mod(highlight_end-1, self.row_width); + } + + const from_cell = cell_rects[highlight_from_column]; + const to_cell = cell_rects[highlight_to_column]; + rl.DrawRectangleRec(rl.Rectangle{ + .x = from_cell.x, + .y = from_cell.y, + .width = (to_cell.x+to_cell.width) - from_cell.x, + .height = (to_cell.y+to_cell.height) - from_cell.y, + }, highlight.color); + } + + for (0..to_column) |column| { + const memory_idx: u32 = row_memory_idx + @as(u32, @intCast(column)); + if (ui.is_holding_mouse(cell_rects[column], &self.selecting)) { + if (self.selecting) { + self.selection_pivot = memory_idx; + selection.start = memory_idx; + selection.end = memory_idx+1; + } + } + + if (self.selecting) { + if (ui.is_mouse_inside_rect(cell_rects[column])) { + if (memory_idx > self.selection_pivot) { + selection.start = self.selection_pivot; + selection.end = memory_idx+1; + } else { + selection.start = memory_idx; + selection.end = self.selection_pivot+1; + } + } + } + + const text_pos = rl.Vector2{ .x = cell_rects[column].x + margin }; + const value = self.memory[memory_idx]; + hex_to_strz(&cell_bufz, value); + var color = self.text_color; + if (self.grey_out_zeros and value == 0) { + color = self.dim_text_color; + } + rl.DrawTextEx(font, &cell_bufz, text_pos, font_size, 0, color); + } + ui.translate(0, font_size); + } + ui.popTransform(); + + return (cell_width + 2*margin)*@as(f32, @floatFromInt(self.row_width)) + middle_margin; + } + + pub fn show_ascii_column(self: *MemoryView, ui: *UI, x: f32, y: f32, selection: *Range, from_row: u32, to_row: u32) f32 { + const font = self.font.*; + const font_size = self.font_size; + const margin = font_size/2; + + const cell_width = rl.MeasureTextEx(font, ".", font_size, 0).x; + const row_count = (to_row - from_row); + + ui.pushTransformT(.{ .ox = x+margin, .oy = y }); + for (0..row_count) |i| { + const row = from_row + @as(u32, @intCast(i)); + + const to_column = @min(self.memory.len - row*self.row_width, self.row_width); + for (0..to_column) |column| { + const memory_idx: u32 = @intCast(row*self.row_width + column); + const value = self.memory[memory_idx]; + var char: u8 = 'A'; + if (std.ascii.isPrint(value)) { + char = value; + } else { + char = '.'; + } + + const pos_x = cell_width * @as(f32, @floatFromInt(column)); + if (selection.start <= memory_idx and memory_idx < selection.end) { + const cell_rect = rl.Rectangle{ + .x = pos_x, + .y = 0, + .height = self.font_size, + .width = cell_width + }; + rl.DrawRectangleRec(cell_rect, rl.RED); + } + + const char_str = [2]u8 { char, 0 }; + rl.DrawTextEx(font, char_str[0..1 :0], rl.Vector2{ .x = pos_x }, font_size, 0, self.text_color); + } + + ui.translate(0, font_size); + } + ui.popTransform(); + + return cell_width*@as(f32, @floatFromInt(self.row_width)) + 2*margin; + } + + pub fn show_scrollbar(self: *MemoryView, ui: *UI, x: f32, y: f32, height: f32) f32 { + const max_scroll = self.get_max_scroll(height); + if (max_scroll == 0) { + return 0; + } + + const visible_row_count = self.get_visible_row_count(height); + const memory_row_count = self.get_memory_row_count(); + const visible_percent = visible_row_count / memory_row_count; + const scroll_percent = self.scroll / max_scroll; + const scrollbar_height = visible_percent * height; + + const scrollbar = rl.Rectangle { + .x = x, + .y = y + scroll_percent * (height - scrollbar_height), + .width = self.font_size, + .height = scrollbar_height + }; + + + var color = rl.BLACK; + _ = ui.is_holding_mouse(scrollbar, &self.scrolling); + if (self.scrolling) { + const mouse_dy = rl.GetMouseDelta().y; + self.scroll += (mouse_dy / (height - scrollbar_height) * max_scroll); + self.scroll = clamp(self.scroll, 0, max_scroll); + color = rl.DARKGRAY; + } + + rl.DrawRectangleRec(scrollbar, color); + + return scrollbar.width; + } +}; + pub fn main() anyerror!void { var program_memory = try std.heap.page_allocator.alloc(u8, megabytes(2)); var fba = std.heap.FixedBufferAllocator.init(program_memory); @@ -33,7 +607,7 @@ pub fn main() anyerror!void { var chip = try ChipContext.init(&allocator); defer chip.deinit(); - chip.set_memory(0x200, @embedFile("ROMs/morse_demo.ch8")); + chip.set_memory(0x200, @embedFile("ROMs/br8kout.ch8")); { // const file = try std.fs.cwd().openFile("ROMs/morse_demo.ch8", .{ .mode = .read_only }); @@ -42,16 +616,22 @@ pub fn main() anyerror!void { } const pixel_size = 20; - const screen_width: i32 = @as(i32, chip.display_width) * pixel_size; - const screen_height: i32 = @as(i32, chip.display_height) * pixel_size; + const initial_screen_width: i32 = @as(i32, chip.display_width) * pixel_size; + const initial_screen_height: i32 = @as(i32, chip.display_height) * pixel_size; - rl.initWindow(screen_width, screen_height, "CHIP-8"); - defer rl.closeWindow(); + rl.SetConfigFlags(rl.ConfigFlags{ .FLAG_WINDOW_RESIZABLE = true }); + rl.InitWindow(initial_screen_width, initial_screen_height, "CHIP-8"); + defer rl.CloseWindow(); - rl.initAudioDevice(); - defer rl.closeAudioDevice(); + rl.InitAudioDevice(); + defer rl.CloseAudioDevice(); - rl.setTargetFPS(60); + rl.SetTargetFPS(60); + + const font_size = 24; + const font_ttf_default_numchars = 95; // TTF font generation default charset: 95 glyphs (ASCII 32..126) + const font = rl.LoadFontEx("src/fonts/generic-mono.otf", font_size, null, font_ttf_default_numchars); + defer rl.UnloadFont(font); const sample_rate = 44100; var data = try allocator.alloc(i16, sample_rate); @@ -64,24 +644,63 @@ pub fn main() anyerror!void { .data = @ptrCast(data.ptr), }; gen_sin_wave(&chip_wave, 440); - var chip_sound = rl.loadSoundFromWave(chip_wave); - defer rl.unloadSound(chip_sound); - rl.setSoundVolume(chip_sound, 0.2); + var chip_sound = rl.LoadSoundFromWave(chip_wave); + defer rl.UnloadSound(chip_sound); + rl.SetSoundVolume(chip_sound, 0.2); var raylib_chip = RaylibChip.init(&chip, &chip_sound); + // var raylib_chip = RaylibChip.init(&chip, null); raylib_chip.tick_speed = 500; raylib_chip.timer_speed = 60; - while (!rl.windowShouldClose()) { - var dt = rl.getFrameTime(); + var edit_mode = false; + var tab: Tab = .MemoryView; + + // var temp_mem = [1]u8{0xAA} ** (16*80 + 10); + // var memory_view = MemoryView.init(&temp_mem, &font, 32); + var memory_view = MemoryView.init(chip.memory, &font, font_size, &allocator); + var selected_memory = Range{}; + var ui = UI.init(); + + while (!rl.WindowShouldClose()) { + const screen_width = rl.GetScreenWidth(); + const screen_height = rl.GetScreenHeight(); + + var dt = rl.GetFrameTime(); raylib_chip.update(dt); - { - rl.beginDrawing(); - defer rl.endDrawing(); + if (rl.IsKeyPressed(rl.KeyboardKey.KEY_TAB)) { + edit_mode = !edit_mode; + } - rl.clearBackground(rl.Color.white); - raylib_chip.render(0, 0, screen_width, screen_height); + if (edit_mode) { + if (rl.IsKeyPressed(rl.KeyboardKey.KEY_ONE)) { + tab = .MemoryView; + } + } + + rl.BeginDrawing(); + defer rl.EndDrawing(); + + if (!edit_mode) { + rl.ClearBackground(rl.DARKGRAY); + + const scale_x = @divFloor(screen_width, chip.display_width); + const scale_y = @divFloor(screen_height, chip.display_height); + const min_scale = @min(scale_x, scale_y); + + const display_width = chip.display_width * min_scale; + const display_height = chip.display_height * min_scale; + const display_x = @divFloor(screen_width - display_width, 2); + const display_y = @divFloor(screen_height - display_height, 2); + raylib_chip.render(display_x, display_y, display_width, display_height); + } else { + rl.ClearBackground(rl.RAYWHITE); + ui.update(); + + if (tab == .MemoryView) { + try memory_view.show(&ui, 0, 0, @floatFromInt(screen_width), @floatFromInt(screen_height), &selected_memory); + } } } } diff --git a/src/raylib-chip.zig b/src/raylib-chip.zig index 02cc77e..e9288bb 100644 --- a/src/raylib-chip.zig +++ b/src/raylib-chip.zig @@ -8,16 +8,16 @@ on_color: rl.Color, off_color: rl.Color, timer_speed: f32, tick_speed: f32, -beep_sound: *rl.Sound, +beep_sound: ?*rl.Sound, tick_time: f32, timer_time: f32, -pub fn init(chip: *ChipContext, beep_sound: *rl.Sound) Self { +pub fn init(chip: *ChipContext, beep_sound: ?*rl.Sound) Self { return Self{ .chip = chip, - .off_color = rl.Color.black, - .on_color = rl.Color.ray_white, + .off_color = rl.BLACK, + .on_color = rl.RAYWHITE, .timer_speed = 60, .tick_speed = 500, .tick_time = 0, @@ -28,26 +28,26 @@ pub fn init(chip: *ChipContext, beep_sound: *rl.Sound) Self { pub fn update_input(self: *Self) void { const keys = [16]rl.KeyboardKey{ - .key_x, - .key_one, - .key_two, - .key_three, - .key_q, - .key_w, - .key_e, - .key_a, - .key_s, - .key_d, - .key_z, - .key_c, - .key_four, - .key_r, - .key_f, - .key_v, + .KEY_X, + .KEY_ONE, + .KEY_TWO, + .KEY_THREE, + .KEY_Q, + .KEY_W, + .KEY_E, + .KEY_A, + .KEY_S, + .KEY_D, + .KEY_Z, + .KEY_C, + .KEY_FOUR, + .KEY_R, + .KEY_F, + .KEY_V, }; for (0.., keys) |i, key| { - self.chip.input[i] = rl.isKeyDown(key); + self.chip.input[i] = rl.IsKeyDown(key); } } @@ -70,14 +70,15 @@ pub fn update(self: *Self, dt: f32) void { self.timer_time -= 1/self.timer_speed; } - const beep_sound = self.beep_sound.*; - if (self.chip.ST > 0) { - if (!rl.isSoundPlaying(beep_sound)) { - rl.playSound(beep_sound); - } - } else { - if (rl.isSoundPlaying(beep_sound)) { - rl.stopSound(beep_sound); + if (self.beep_sound) |beep_sound| { + if (self.chip.ST > 0) { + if (!rl.IsSoundPlaying(beep_sound.*)) { + rl.PlaySound(beep_sound.*); + } + } else { + if (rl.IsSoundPlaying(beep_sound.*)) { + rl.StopSound(beep_sound.*); + } } } } @@ -86,14 +87,14 @@ pub fn render(self: *Self, x: i32, y: i32, width: i32, height: i32) void { const pixel_width = @divFloor(width, self.chip.display_width); const pixel_height = @divFloor(height, self.chip.display_height); - rl.drawRectangle(0, 0, width, height, self.off_color); + rl.DrawRectangle(x, y, width, height, self.off_color); for (0..self.chip.display_height) |oy| { for (0..self.chip.display_width) |ox| { if (self.chip.display_get(@intCast(ox), @intCast(oy))) { const ix = x + @as(i32, @intCast(ox)) * pixel_width; const iy = y + @as(i32, @intCast(oy)) * pixel_height; - rl.drawRectangle(ix, iy, pixel_width, pixel_height, self.on_color); + rl.DrawRectangle(ix, iy, pixel_width, pixel_height, self.on_color); } } }