From f595d4f43c46cedeaca90829ece4976da424ba36 Mon Sep 17 00:00:00 2001 From: stjet <49297268+stjet@users.noreply.github.com> Date: Sat, 12 Oct 2024 06:18:05 +0000 Subject: [PATCH] ported ming-os graphics to linux framebuffer --- .gitignore | 2 + Cargo.toml | 11 + bmps/mingde.bmp | Bin 0 -> 6538 bytes bmps/times-new-roman/!0.bmp | Bin 0 -> 150 bytes "bmps/times-new-roman/\"0.bmp" | Bin 0 -> 154 bytes bmps/times-new-roman/$2.bmp | Bin 0 -> 446 bytes bmps/times-new-roman/(0.bmp | Bin 0 -> 374 bytes bmps/times-new-roman/)0.bmp | Bin 0 -> 374 bytes bmps/times-new-roman/*0.bmp | Bin 0 -> 222 bytes bmps/times-new-roman/,9.bmp | Bin 0 -> 114 bytes bmps/times-new-roman/-8.bmp | Bin 0 -> 70 bytes bmps/times-new-roman/.9.bmp | Bin 0 -> 70 bytes bmps/times-new-roman/00.bmp | Bin 0 -> 474 bytes bmps/times-new-roman/10.bmp | Bin 0 -> 330 bytes bmps/times-new-roman/20.bmp | Bin 0 -> 522 bytes bmps/times-new-roman/30.bmp | Bin 0 -> 426 bytes bmps/times-new-roman/40.bmp | Bin 0 -> 522 bytes bmps/times-new-roman/50.bmp | Bin 0 -> 474 bytes bmps/times-new-roman/60.bmp | Bin 0 -> 474 bytes bmps/times-new-roman/70.bmp | Bin 0 -> 474 bytes bmps/times-new-roman/80.bmp | Bin 0 -> 474 bytes bmps/times-new-roman/90.bmp | Bin 0 -> 474 bytes bmps/times-new-roman/:4.bmp | Bin 0 -> 118 bytes bmps/times-new-roman/?0.bmp | Bin 0 -> 342 bytes bmps/times-new-roman/A0.bmp | Bin 0 -> 630 bytes bmps/times-new-roman/B1.bmp | Bin 0 -> 494 bytes bmps/times-new-roman/C0.bmp | Bin 0 -> 534 bytes bmps/times-new-roman/D1.bmp | Bin 0 -> 538 bytes bmps/times-new-roman/E1.bmp | Bin 0 -> 494 bytes bmps/times-new-roman/F1.bmp | Bin 0 -> 450 bytes bmps/times-new-roman/G0.bmp | Bin 0 -> 630 bytes bmps/times-new-roman/H1.bmp | Bin 0 -> 582 bytes bmps/times-new-roman/I1.bmp | Bin 0 -> 274 bytes bmps/times-new-roman/J0.bmp | Bin 0 -> 342 bytes bmps/times-new-roman/K1.bmp | Bin 0 -> 582 bytes bmps/times-new-roman/L1.bmp | Bin 0 -> 494 bytes bmps/times-new-roman/M1.bmp | Bin 0 -> 714 bytes bmps/times-new-roman/N0.bmp | Bin 0 -> 630 bytes bmps/times-new-roman/O0.bmp | Bin 0 -> 582 bytes bmps/times-new-roman/P1.bmp | Bin 0 -> 450 bytes bmps/times-new-roman/Q0.bmp | Bin 0 -> 714 bytes bmps/times-new-roman/R1.bmp | Bin 0 -> 538 bytes bmps/times-new-roman/S0.bmp | Bin 0 -> 438 bytes bmps/times-new-roman/T1.bmp | Bin 0 -> 494 bytes bmps/times-new-roman/U0.bmp | Bin 0 -> 630 bytes bmps/times-new-roman/V0.bmp | Bin 0 -> 630 bytes bmps/times-new-roman/W0.bmp | Bin 0 -> 822 bytes bmps/times-new-roman/X1.bmp | Bin 0 -> 582 bytes bmps/times-new-roman/Y1.bmp | Bin 0 -> 582 bytes bmps/times-new-roman/Z1.bmp | Bin 0 -> 494 bytes bmps/times-new-roman/a4.bmp | Bin 0 -> 362 bytes bmps/times-new-roman/b0.bmp | Bin 0 -> 522 bytes bmps/times-new-roman/c4.bmp | Bin 0 -> 330 bytes bmps/times-new-roman/d0.bmp | Bin 0 -> 522 bytes bmps/times-new-roman/e4.bmp | Bin 0 -> 362 bytes bmps/times-new-roman/f0.bmp | Bin 0 -> 474 bytes bmps/times-new-roman/g4.bmp | Bin 0 -> 522 bytes bmps/times-new-roman/h0.bmp | Bin 0 -> 522 bytes bmps/times-new-roman/i0.bmp | Bin 0 -> 330 bytes bmps/times-new-roman/j2.bmp | Bin 0 -> 458 bytes bmps/times-new-roman/k0.bmp | Bin 0 -> 570 bytes bmps/times-new-roman/l0.bmp | Bin 0 -> 330 bytes bmps/times-new-roman/m4.bmp | Bin 0 -> 554 bytes bmps/times-new-roman/n4.bmp | Bin 0 -> 394 bytes bmps/times-new-roman/o4.bmp | Bin 0 -> 362 bytes bmps/times-new-roman/p4.bmp | Bin 0 -> 522 bytes bmps/times-new-roman/q4.bmp | Bin 0 -> 522 bytes bmps/times-new-roman/r4.bmp | Bin 0 -> 330 bytes bmps/times-new-roman/s4.bmp | Bin 0 -> 298 bytes bmps/times-new-roman/t2.bmp | Bin 0 -> 338 bytes bmps/times-new-roman/u4.bmp | Bin 0 -> 426 bytes bmps/times-new-roman/v4.bmp | Bin 0 -> 394 bytes bmps/times-new-roman/w4.bmp | Bin 0 -> 522 bytes bmps/times-new-roman/x4.bmp | Bin 0 -> 394 bytes bmps/times-new-roman/y4.bmp | Bin 0 -> 490 bytes bmps/times-new-roman/z4.bmp | Bin 0 -> 362 bytes bmps/times-new-roman/{0.bmp | Bin 0 -> 374 bytes bmps/times-new-roman/}0.bmp | Bin 0 -> 374 bytes bmps/times-new-roman/█1.bmp | Bin 0 -> 618 bytes src/components/highlight_button.rs | 78 +++ src/components/mod.rs | 21 + src/components/toggle_button.rs | 90 ++++ src/framebuffer.rs | 228 +++++++++ src/fs.rs | 52 ++ src/keyboard.rs | 109 +++++ src/main.rs | 37 ++ src/messages.rs | 94 ++++ src/themes.rs | 44 ++ src/window_likes/desktop_background.rs | 44 ++ src/window_likes/lock_screen.rs | 79 +++ src/window_likes/minesweeper.rs | 350 ++++++++++++++ src/window_likes/mod.rs | 9 + src/window_likes/start_menu.rs | 191 ++++++++ src/window_likes/taskbar.rs | 128 +++++ src/window_likes/terminal.rs | 116 +++++ src/window_likes/workspace_indicator.rs | 74 +++ src/window_manager.rs | 613 ++++++++++++++++++++++++ 97 files changed, 2370 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 bmps/mingde.bmp create mode 100644 bmps/times-new-roman/!0.bmp create mode 100644 "bmps/times-new-roman/\"0.bmp" create mode 100644 bmps/times-new-roman/$2.bmp create mode 100644 bmps/times-new-roman/(0.bmp create mode 100644 bmps/times-new-roman/)0.bmp create mode 100644 bmps/times-new-roman/*0.bmp create mode 100644 bmps/times-new-roman/,9.bmp create mode 100644 bmps/times-new-roman/-8.bmp create mode 100644 bmps/times-new-roman/.9.bmp create mode 100644 bmps/times-new-roman/00.bmp create mode 100644 bmps/times-new-roman/10.bmp create mode 100644 bmps/times-new-roman/20.bmp create mode 100644 bmps/times-new-roman/30.bmp create mode 100644 bmps/times-new-roman/40.bmp create mode 100644 bmps/times-new-roman/50.bmp create mode 100644 bmps/times-new-roman/60.bmp create mode 100644 bmps/times-new-roman/70.bmp create mode 100644 bmps/times-new-roman/80.bmp create mode 100644 bmps/times-new-roman/90.bmp create mode 100644 bmps/times-new-roman/:4.bmp create mode 100644 bmps/times-new-roman/?0.bmp create mode 100644 bmps/times-new-roman/A0.bmp create mode 100644 bmps/times-new-roman/B1.bmp create mode 100644 bmps/times-new-roman/C0.bmp create mode 100644 bmps/times-new-roman/D1.bmp create mode 100644 bmps/times-new-roman/E1.bmp create mode 100644 bmps/times-new-roman/F1.bmp create mode 100644 bmps/times-new-roman/G0.bmp create mode 100644 bmps/times-new-roman/H1.bmp create mode 100644 bmps/times-new-roman/I1.bmp create mode 100644 bmps/times-new-roman/J0.bmp create mode 100644 bmps/times-new-roman/K1.bmp create mode 100644 bmps/times-new-roman/L1.bmp create mode 100644 bmps/times-new-roman/M1.bmp create mode 100644 bmps/times-new-roman/N0.bmp create mode 100644 bmps/times-new-roman/O0.bmp create mode 100644 bmps/times-new-roman/P1.bmp create mode 100644 bmps/times-new-roman/Q0.bmp create mode 100644 bmps/times-new-roman/R1.bmp create mode 100644 bmps/times-new-roman/S0.bmp create mode 100644 bmps/times-new-roman/T1.bmp create mode 100644 bmps/times-new-roman/U0.bmp create mode 100644 bmps/times-new-roman/V0.bmp create mode 100644 bmps/times-new-roman/W0.bmp create mode 100644 bmps/times-new-roman/X1.bmp create mode 100644 bmps/times-new-roman/Y1.bmp create mode 100644 bmps/times-new-roman/Z1.bmp create mode 100644 bmps/times-new-roman/a4.bmp create mode 100644 bmps/times-new-roman/b0.bmp create mode 100644 bmps/times-new-roman/c4.bmp create mode 100644 bmps/times-new-roman/d0.bmp create mode 100644 bmps/times-new-roman/e4.bmp create mode 100644 bmps/times-new-roman/f0.bmp create mode 100644 bmps/times-new-roman/g4.bmp create mode 100644 bmps/times-new-roman/h0.bmp create mode 100644 bmps/times-new-roman/i0.bmp create mode 100644 bmps/times-new-roman/j2.bmp create mode 100644 bmps/times-new-roman/k0.bmp create mode 100644 bmps/times-new-roman/l0.bmp create mode 100644 bmps/times-new-roman/m4.bmp create mode 100644 bmps/times-new-roman/n4.bmp create mode 100644 bmps/times-new-roman/o4.bmp create mode 100644 bmps/times-new-roman/p4.bmp create mode 100644 bmps/times-new-roman/q4.bmp create mode 100644 bmps/times-new-roman/r4.bmp create mode 100644 bmps/times-new-roman/s4.bmp create mode 100644 bmps/times-new-roman/t2.bmp create mode 100644 bmps/times-new-roman/u4.bmp create mode 100644 bmps/times-new-roman/v4.bmp create mode 100644 bmps/times-new-roman/w4.bmp create mode 100644 bmps/times-new-roman/x4.bmp create mode 100644 bmps/times-new-roman/y4.bmp create mode 100644 bmps/times-new-roman/z4.bmp create mode 100644 bmps/times-new-roman/{0.bmp create mode 100644 bmps/times-new-roman/}0.bmp create mode 100644 bmps/times-new-roman/█1.bmp create mode 100644 src/components/highlight_button.rs create mode 100644 src/components/mod.rs create mode 100644 src/components/toggle_button.rs create mode 100644 src/framebuffer.rs create mode 100644 src/fs.rs create mode 100644 src/keyboard.rs create mode 100644 src/main.rs create mode 100644 src/messages.rs create mode 100644 src/themes.rs create mode 100644 src/window_likes/desktop_background.rs create mode 100644 src/window_likes/lock_screen.rs create mode 100644 src/window_likes/minesweeper.rs create mode 100644 src/window_likes/mod.rs create mode 100644 src/window_likes/start_menu.rs create mode 100644 src/window_likes/taskbar.rs create mode 100644 src/window_likes/terminal.rs create mode 100644 src/window_likes/workspace_indicator.rs create mode 100644 src/window_manager.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2c96eb1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +target/ +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..ad6e375 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "ming-os-kernel" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +blake2 = { version = "0.10.6", default-features = false } +linux_framebuffer = { package = "framebuffer", version = "0.3.1" } +bmp-rust = "0.4.1" diff --git a/bmps/mingde.bmp b/bmps/mingde.bmp new file mode 100644 index 0000000000000000000000000000000000000000..c2986cd8bd0366c206876a70aaa346f7a4fb74e1 GIT binary patch literal 6538 zcmeHKT`Z(m7`E9)usdU=Ym;8sv^R|#X>cJ(6B(9CS}kr?^e>xDG(pOS8-KN7+p4Te ze_U9aiL|kGv0B@;VHsIw*=5TvN)sX|(p4*iVljAno^O0#2Qy4&##QIUnRCv2-gD0L zz5g`vWv_ThtxWtDh#t7xPk6lQagsY?Kf>GGximL;c$ojVgeER2yP|V7FtPc(|M}16 zPlSeszMPtx`nu?zRaI5qZ~FXkGVuM1kln-5@RH?1%+)=BySruJwgI;d9BBiOcA&cd z4^*9N@O`)i_3qlYYxCG+h+i-8TEDq-egLb$;#YMaULRkBSMCj{ziGj#8wPkLq{6rO zN1QRdIC#d1*ysNki25BU_!DSd1SZyj*$s2&oHXu{hrFr|A+z_cK>gD&yi0Gv`^yIS zB&Ooj4^N=FJq>m1g3IuQ3;^HH0rQ){4s18>k*7xR20vPX=cNLun&;s2)g5?WzYYJ3 z>C)V*P>0jM3XD$3i)YUYy?rtP7)`b@+1lZm^gBH`S7X4Liy0EG)4E>x>U!Xp@dZ?E zMyK!*TfWfO@|Gf3^p0Pal?c}-Cf}u z51hs%+XL;*mQJUprcihDCKeVJWC+T*wY7!1x;i}Q>QZQ&{Qbg&;6B&H6#5>0!B#Vw zOt{t1fb5(cEG;eVVH+A6LQqf;KKSq>tgj2FwrKPj-Yv_((UI|iK6q67`rGeN_-T>C zwkWU`6<@=<35ke`io)F7-jC#+yxUP+@;Sz^!(s>l?dCEeE6)*>w}4V#<)34CT| z1`!bvn4O*7e~lEw9mU0S=Y)Ms?ll|fjo!z<;T@s3PJ=<}7h+@4+}wQw}XghKp3A+b@H z#l^+Y>2w$#9tQD6M@L)5$GiE4Jy@*q*upnHK8}Qh1PKpynURr!%F0ScH^j~74+_ty3k(HH&yu3WzYi-5g;1D`GIwV_I zSy_?ACXKF&FN~qjMeSkP=pTVWZ=j^41m(g=U*DpE_Ba$7!VL3WI3o{=7c%Z75R;N zKde63^Qc%5m!Yd0_uBNRsi^_|xvZ=VMx#+-Wv#(GZP}C~bXV0^tkHOvBNz@l@7$4oUQLEx8uTQ`AxJP+Bf}Lu2U4_vY2p>Jmht?y; n5dw*6k?QGREj%LzSXC8|B%hM?YBXqJ;Lu~mh8+h^T$nN4|AiHu literal 0 HcmV?d00001 diff --git "a/bmps/times-new-roman/\"0.bmp" "b/bmps/times-new-roman/\"0.bmp" new file mode 100644 index 0000000000000000000000000000000000000000..45b4972e6bf4e3b571c10b20a6013c3cdc8ba672 GIT binary patch literal 154 zcmZ?roy7nFWFgkib>KF6yf=v zw{y(Q+dlXH-Z}T(_wK9JcM%s}CB_1xNFw6$jLr?UAKvH?1c5V@Jj*h3wXnn#N6b*w zo=+}n_Q+|R(ZI;|A%}Z1_aObupPoGLTm5U#CiNFq)+5W2ZjcW=Af@Zeg9c9u7&vRGq)EhgBbV(%u)zgoy_wKtcU BiS7UZ literal 0 HcmV?d00001 diff --git a/bmps/times-new-roman/(0.bmp b/bmps/times-new-roman/(0.bmp new file mode 100644 index 0000000000000000000000000000000000000000..07a6a62d38a77710d149760934ade2349f85c22a GIT binary patch literal 374 zcmZ?rEn{Q=12Z700mQ68EC9re3<^M!fx!VP59UCL|Ns9pz%Woq6^ILw_>MsSR3K&n zi2;EY5MKad9wasUf&5Y=z8jDaQmla_HXF$Q48)8`VmE;ND= F1OSr>|e6S^|m$Jcr-`w6OFTf|WP15sQF@BIFPjqV4)-adDY} zH#6Vc{FaNglTc&&05a%;lRjN!@;eXV-&s_}+mQ8n;hUKqxIv-UP;EHCG`VL> I&STd31py9?mH+?% literal 0 HcmV?d00001 diff --git a/bmps/times-new-roman/*0.bmp b/bmps/times-new-roman/*0.bmp new file mode 100644 index 0000000000000000000000000000000000000000..05f21b49ac1ac4843bcc149a5168f380c7cdec7c GIT binary patch literal 222 zcmZ?ry~h9nWg)crhFR literal 0 HcmV?d00001 diff --git a/bmps/times-new-roman/,9.bmp b/bmps/times-new-roman/,9.bmp new file mode 100644 index 0000000000000000000000000000000000000000..949d3e9de3359248625fd43618851e870e4173dd GIT binary patch literal 114 zcmZ?rEnI3P6&9!3IoX5ZOQxumAu5Gk`D2M`|z T;@d#{6^LH|@j4*>0L1YCZ(I(% literal 0 HcmV?d00001 diff --git a/bmps/times-new-roman/-8.bmp b/bmps/times-new-roman/-8.bmp new file mode 100644 index 0000000000000000000000000000000000000000..6631a2247ac635e165558f6160900c68cb24031b GIT binary patch literal 70 kcmZ?rbz^`4Ga#h_#4JF}2*wIPl7T@0OkxoKfg*4W0CS@SW&i*H literal 0 HcmV?d00001 diff --git a/bmps/times-new-roman/.9.bmp b/bmps/times-new-roman/.9.bmp new file mode 100644 index 0000000000000000000000000000000000000000..665aada1aeac235496181bd520e74f9828294716 GIT binary patch literal 70 ocmZ?rbz^`4Ga#h_#7t1k$e;jZF)#>#Neto$P~n+a literal 0 HcmV?d00001 diff --git a/bmps/times-new-roman/00.bmp b/bmps/times-new-roman/00.bmp new file mode 100644 index 0000000000000000000000000000000000000000..0a297ff1ca2d7f614b49427e8ae0cc56651aac11 GIT binary patch literal 474 zcmZ?ry~W4?23+ehn`K1G!M}pMe2_K>Yts?m@*3hrVhg z?2r>xXnoD-U~DYhkd~(Ake0@_VCGCt8?NijDlz>=Fx?=U1(^e6|Njp(kbyxCh*tq| zHxSPQVsWTAE0BE=h<%}aHz4~A5QCf;48-q&mZv`~;8~5T5|zP>^0AZ~@}8K+FQ=ivZb6fOra&76yp}05{u%m;e9( literal 0 HcmV?d00001 diff --git a/bmps/times-new-roman/10.bmp b/bmps/times-new-roman/10.bmp new file mode 100644 index 0000000000000000000000000000000000000000..22a4f15efe116832ba95630870fbd97c0a0b2d47 GIT binary patch literal 330 zcmZ?r^#q%mc)X37d>ui=GYAQKAyGcZ69i2vWoJ*c?h&{vIw z9de=yt*;p!jE#jG($dr%($d%#%$&(-!*!imC8pm9rW-_q>_GzoK>igVehk2X5IuCA4n$v@l_-~E0CQJ#LGeM0RU95V!HqU literal 0 HcmV?d00001 diff --git a/bmps/times-new-roman/20.bmp b/bmps/times-new-roman/20.bmp new file mode 100644 index 0000000000000000000000000000000000000000..ba52be3881444f35863192ee4e39bf0bce216a1c GIT binary patch literal 522 zcmZwCze@sf7{~GN<&X4G2qYQ=GYC;fjV1?iX>t)v1P7OH4_AqEXlbZTa1a_?goa$S zweTNkYHkh(hvw`3cs_6(eereAj}KpuYc%ZxvpWwwhe#j=%j8Xj>RQIUq_DV~nEAia8tTW6U#@bQi8Tci~Dl#~{4dnXHQq t4soh`t?aHLA9jHolr@ved4)7*$lw!q=wkuN7J7%igcY{<#v@Xa{{b}nsUrXY literal 0 HcmV?d00001 diff --git a/bmps/times-new-roman/30.bmp b/bmps/times-new-roman/30.bmp new file mode 100644 index 0000000000000000000000000000000000000000..605f7981e01af2f76fe61a6d92b0a828f954ef56 GIT binary patch literal 426 zcmZ?rUB$=%23Yts?m@*3hrVhg z?2r>xXnoD-U~DYhkd~(Ake0@_VCGCt8?NijDlz>=Fx?=U1(^e3Zv;}`fp|X<&je!q z|NnuOFfe2Su{o5@4rH$ZVr>u~83V=afp{IV7*vlVQ2Zqp@jjsV2dEm5bF_gN6byfW zcsW#@56G?t;`cy&2#AG|%>nYQfjAvm48{hEKLBD)AT~l01KIN$hzo!?6o`v~*c6Co X0Wrw4E+Dl)PzuB^f%rKPSAxUFa%mc)X3n;0=8%@gwqWK=P8+W4%qlVcMljtVngy8yWdHyF9|c2|0BvE& zMwbKeNtFkxzl(4h-20NEffptujnPXppa zeC`9vF9TvVAjZ%S)h7y++YH2+ehn`K1G!M}pMe2_K>Yts?m@*3hrVhg z?2r>xXnoD-U~DYhkd~(Ake0@_VCGCt8?NijDlz>=Fx?=U1(^e3_XDYqK>P-Xw*s-p z|Ns9PKo|%X0&yh}a{)0(YZeeU!{pIvpxhiFc10J5nw0{STY*Ck6b#4E<$z*JKn!x* zB4jxr-v@{v1MxQ?)&ydAAl?APATf}`zW{MC)LscF{{oQi0Ad+5v-*HyCdgtiHc%X- VcQ+81BVi*X`Cgzn$UYc81OSi{oYw#V literal 0 HcmV?d00001 diff --git a/bmps/times-new-roman/60.bmp b/bmps/times-new-roman/60.bmp new file mode 100644 index 0000000000000000000000000000000000000000..8bb175e8905b1e9e9fed7d87684c465789f3bb80 GIT binary patch literal 474 zcmaLTF-QVY9LMo@Sc<7rv^1oHq>CszxHQNixG0K4Q-jkjjt(srw-o3ifrKvNR-jXp ztN2nMas)aEtsFOxcYi literal 0 HcmV?d00001 diff --git a/bmps/times-new-roman/70.bmp b/bmps/times-new-roman/70.bmp new file mode 100644 index 0000000000000000000000000000000000000000..3ab4e732a3b0d2b6bf4d3c483ddd0cd06f64cff0 GIT binary patch literal 474 zcmZ?ry~W4?23+ehn`K1G!M}pMe2_K>Yts?m@*3hrVhg z?2r>xXnoD-U~DYhkd~(Ake0@_VCGCt8?NijDlz>=Fx?=U1(^e6|Ns9VjDgG&ATERP zk!hf~G7uj}7DHwOuS{NUh28y!+@e?5C0^$f{IS?BN76EYp5H|wxd?3yR;xs5;2&6$+ O35fpzF-QY4ehUEoDz+>D literal 0 HcmV?d00001 diff --git a/bmps/times-new-roman/80.bmp b/bmps/times-new-roman/80.bmp new file mode 100644 index 0000000000000000000000000000000000000000..d2a0f312d9a66894ccde4cbe579a273fe0ee29a4 GIT binary patch literal 474 zcmYk(KS)AB9Ki8=8b(r6I2sNj&DkK*ATCZpN~42IHwZN(QA5~ksG%iDMRdto-q2QK zIJER<4IG0avemWo`|I6-$NRkd{ki+`;1##Wp88)`yic)+Wq2y3qU<~LgOw1eT}G+! zk=ztZm4-i>uGb#c5~+)cSIA~peXTPEtz&~;Z)q$39!*!TH{5O67v_^NmeM(Haf~MR zA)g_iH$uc1qXbLN`uN3xtJ_|g+84%XxE^r~kNAX~d4|l7;|?VV+w7k0 z<%xgrhxXk3P4X!|kV6Z;y>n0ejik*Z#IMkJ!KIsH`#01A9AW5s#QYKUggwZNLv(S5 IKI(FI2EM9rmH+?% literal 0 HcmV?d00001 diff --git a/bmps/times-new-roman/90.bmp b/bmps/times-new-roman/90.bmp new file mode 100644 index 0000000000000000000000000000000000000000..cc6a555a2a062cfab773e69b6777abcfe6b0a4e7 GIT binary patch literal 474 zcmZ?ry~W4?23+ehn`K1G!M}pMe2_K>Yts?m@*3hrVhg z?2r>xXnoD-U~DYhkd~(Ake0@_VCGCt8?NijDlz>=Fx?=U1(^e3y923wAnpNT<^TWx zGoWLjxI7S_1!5m`IS?NR)PVRC5If_LZvx6a1!8`vdJQ1E8i@0N805acK%5Q4RzUm} zh*tu!D-ah0@f#pM48#Y3_&E@>!|VXEmjm$&ApQu%GhyOTJEj1|en92x(B#$v#qR*| sIw0;slLLuQ1Y!dqz5>J?Q1g6%?AJgn3FU*rZ7C2>2jV?ItO^nb0B;tJ5&!@I literal 0 HcmV?d00001 diff --git a/bmps/times-new-roman/:4.bmp b/bmps/times-new-roman/:4.bmp new file mode 100644 index 0000000000000000000000000000000000000000..fe835514747c105732b3730e19567fa54d8b2ddb GIT binary patch literal 118 zcmZ?rEn|QHGa#h_#7scU0mO_93P6&9!2wKS5Ql*x2Y`415YPGl|33ph3{*22h^GPZ IQ6N4E0D-L^EdT%j literal 0 HcmV?d00001 diff --git a/bmps/times-new-roman/?0.bmp b/bmps/times-new-roman/?0.bmp new file mode 100644 index 0000000000000000000000000000000000000000..cfb9e10839905952c9cd2a6ed20126b7507a1ee3 GIT binary patch literal 342 zcmZ?r4P#^g12Z700mN)T%mc)X3<^M!fk6Q(59UCL|NsAkSWG~C7Kj@`d>97uK{|E- zaS==mMgzszU~FU>DDDHqLdaquHV~)+aS*yVRBjVcP8?khC>8_6o3V(4y!IT3V}ZB@ zh`nHHfb7{odPT z6KvnP>dUwFHFwE!arcX#VeYHHC7&Vr$yaZI(*t?$={Hw>`6oB^3%`6lbHLyHh1P{B8_hzo*M zOxOPzhbaTu;CH#_+>f(6)@VH)6FxQ8GF)Wgm=dco8$Az;@6u&^fyEI^EU?2Kp0(Bp z9v`x){Pbk%So^-_ym(IgY5Fe&3Ar4ZH}3y(oMj;Ql>3Zgxi!5BR(RlsretEds@5sa x>$u_sH*j4=QJ literal 0 HcmV?d00001 diff --git a/bmps/times-new-roman/C0.bmp b/bmps/times-new-roman/C0.bmp new file mode 100644 index 0000000000000000000000000000000000000000..b6771845ea94cac459e970cb4a814e13e734ca13 GIT binary patch literal 534 zcmZwDu?s;_6vy%VkO70j+muWe*-Qr6rThi{0T~Sz#cX4eWMQx=e}Zhx2ED;xu^2># z=X+n>I&SaQ=bn3h=hTZv(~YR$RJlt?AO%MixRvspp!{9F?< z#AR;3nfIW7N8O&8o>>P;T(C8{tmOKCTj`8bhKD{PW{-CsXUs&Fp&wy~1?HHbg~1Q~ CkJw!R literal 0 HcmV?d00001 diff --git a/bmps/times-new-roman/D1.bmp b/bmps/times-new-roman/D1.bmp new file mode 100644 index 0000000000000000000000000000000000000000..d68b91a2b8ce3706604758550fbe287d40000e8c GIT binary patch literal 538 zcmZ?rm11H512Z700mR%;%*dbsWHB&2VFXj?L?lq)3K0JX;v+yjABblF@g5+a2E^+B z|Nm!zVIaQ_h(T)Qki^)4{B|Jz3B)l-;(0(midrPO#ZdKnATc1orxxTLkfJ4o)I#Oo z0M$MrQtd6E+Mh(KeG63kkdRuS8f_p3xpxH-YF7Z&{RLu6sM<29UgYrQ0*Xz9sw;-7 civaR3Le-uJ(japd0P#^Eo(IHMAax)N079F=ssI20 literal 0 HcmV?d00001 diff --git a/bmps/times-new-roman/E1.bmp b/bmps/times-new-roman/E1.bmp new file mode 100644 index 0000000000000000000000000000000000000000..41558b35789e0a0aec3ea96441c3636fb355ce62 GIT binary patch literal 494 zcmaLUJqp4=5QgE6AVN}z_KKyAt%aqXg^i%#A0EO>1abmJ5HDb36ST1N1fIjnR@ZmJ zh#S}#cy=c<9|@cEhmXKySC_So5D@~?Vl^h!d3N(#=9D*B+_A+5dtC5B$66Vi6{MKT zy^G!evF65_8@ZWp&208B$GnP?llMN4%o?F5ccdSy1N*Zex1?WFP4-hkE_V(xJybmJ i&E@or4YHBe3u^g(zFb1TjtQ=i+!^vO#11LmD1QKZ9-S5d literal 0 HcmV?d00001 diff --git a/bmps/times-new-roman/F1.bmp b/bmps/times-new-roman/F1.bmp new file mode 100644 index 0000000000000000000000000000000000000000..86622f4ebf8ca4e2bb1e3d9a4a4a698244d42ec7 GIT binary patch literal 450 zcmZ?rJ;cZW24+A~1Bf|+m>Y;085Dpd149o~9?U@y5kTevApQ@;7l1e(h`Im&|IdJo zfnvcx3{q2sERM_u%6UW8q#=tVvvH{bio@;1rUod+1;n{9vyjvr0VhXL6j0TeYrJ{u5k2jXZTJ_y7hH83}W M+`Sx#ZvycP07;ylivR!s literal 0 HcmV?d00001 diff --git a/bmps/times-new-roman/G0.bmp b/bmps/times-new-roman/G0.bmp new file mode 100644 index 0000000000000000000000000000000000000000..209d3670239f9c4deaa9fc75777c3a754cdfed94 GIT binary patch literal 630 zcmZ{iKMO%o7>5r7$xVq5Ko*NdA)D+rqs1VLg~7Go3Z^*g5T91%-`^Tzhes6&Pto~|%Kc@Aa&i|-R1^A0v zzYs(4Qf>MD*0GMgztDpmO0zV)RnK18Db5Gz=00 u`F9}PLu~jYLEVTg{3D@qiJ zKx_oWcY&A{DlP$Ja{#d%5Q7vt!Sq1oVdBX+#Pe{7=hInS8|wZFn7u&0EfBAQieCfL F(EyEmZW#ao literal 0 HcmV?d00001 diff --git a/bmps/times-new-roman/K1.bmp b/bmps/times-new-roman/K1.bmp new file mode 100644 index 0000000000000000000000000000000000000000..c4615d0fb37b8ca7e4cf2a8032f4b73ead612df8 GIT binary patch literal 582 zcmZvZy9&ZU5Jfk3K0py0J4?YLl>zOvh?O5;p{4j}1Z#i8uduQc#X=CQ6#O1Bo{?D{ zLIP*^&dkYUNq-pU-1mxDn^1xx1g^pATt(;K;=hcDcSv9dy(E$7>aYhXgey#Sw$ffC zwf6G#F7Sp81ZFm)mS?ooEs&2O?!tS1r}y*8;+{aze>mK@RkQcT6oLq$m#J;D`8q>@Azaf43 Lr*i%+gyMeyc4^QW literal 0 HcmV?d00001 diff --git a/bmps/times-new-roman/L1.bmp b/bmps/times-new-roman/L1.bmp new file mode 100644 index 0000000000000000000000000000000000000000..ebf012f20084e1958d29a02b8bbce2db8ebadc22 GIT binary patch literal 494 zcmZ?reaFZE24+A~1BkhRm>Y;085Dpd1H%reJeY$ZB7w}~K>QzwUjy-OAYKE+2Z8t& z5QD^Z{{IJ245?6lGKi0if#TsnTn|+j0#z4;tOmvg$`uo-j*%F3Nao_TcMzzHg@y}8 X_#wFs6plxr>Mj6jP?(Fuavc@DVup$t2a3wzul+}V6t{9g&_3|^rPTWOjS z!YK@tTYW-(4;nr}V*MU@-|2GHZ}17*u75@?Z`bv-@6y#x)4XT;E$g?PHtT&v^9{T3 z3`J= DXp8Ni literal 0 HcmV?d00001 diff --git a/bmps/times-new-roman/N0.bmp b/bmps/times-new-roman/N0.bmp new file mode 100644 index 0000000000000000000000000000000000000000..ec59ed325c78de9f1f603ca7d073908ff9f304a3 GIT binary patch literal 630 zcmajbu?qoV7{~GV$aaIWn8YGwR8}%jO4$s`WHA^lH<;xw@JHB{Zjp_}Vq#;GGBQ|n zzOSCk+jY*>=k0l)=XY=SUajTC)aNO)7GMiYWvP^^%KWqWE;&jEJB*R{JWrYERWJ15QWzS3yDPnBAP*JSEupBrH1iH7e$Qt7KK#N9^gu3AvD{bug63`X8~!2YcWlOggcB^_ z2s_K}DftHew7b36F^|q(dq3zIqL%rJAA<|XUArLvR`NSGY;085Dpd149o~9?U@y5kTeGKLaub zip2vlNKGoTI5Hb3=MPmAi!6@JCRGhkJsS{nBbxzYBbl=T$gTk51wi}^h}(gf15FLw zJwUlsAO;yQ0goCUsG1je)JQ?qJcFtMg&W9Ruw6i!35aJx#R`FRI1nF%@^1p^bwGR! Lhz|jAB*+W^%bJ$I literal 0 HcmV?d00001 diff --git a/bmps/times-new-roman/Q0.bmp b/bmps/times-new-roman/Q0.bmp new file mode 100644 index 0000000000000000000000000000000000000000..d1a5367a71e000fe0b3b055daae9ecc2f83850c7 GIT binary patch literal 714 zcmZ|Mu}eZx6vy$axMYJu*boE_flY!0<|qykL9?riLxY1;(3XR^=hWgFt_}|V2Z9bM zP1PcZiIbS11kdl0d*CA12cLJ({hh;m-e>-y7dE>q!~F`!h#_PyxEs^3{oTgif*>%5 zFmj11D!BK}k)NQ61#W#al5wT*4cQcZbL3GxV}LeBctOmaXD`nhqr8`w$bOKt?~GGV z&~oRrCV#^hJt@LMDVkNb2Ml<^I zygz(mgNXLrm0Zpw=NY1I>$E$gHN89Aud$xu7Y*&ZEBQO7NJ!3g1DP-A9CZkfm?NvX Soj)UcMF%Z(A^*d<&TRpxt|m+X literal 0 HcmV?d00001 diff --git a/bmps/times-new-roman/R1.bmp b/bmps/times-new-roman/R1.bmp new file mode 100644 index 0000000000000000000000000000000000000000..9eca796997e707f7b68cb73f935ac14e33a94f73 GIT binary patch literal 538 zcma)(u?vAg7>Dnp7%T=BQ6^cevPfYu7?gqkfKgdY4E_PD*}q_uVlYV=tfp@@n$USp z@732kxq9y2``vThb=U0XV&>PX^kqm1fhkfM69&IQcSC|JSlB?rT5BA%9=PHRBgtlx zRU%%2>;XGSM)I+^CD|KieuNM*-jg-$E^t23ih9~*y$)|!YEQXqoGm2G_*sPf(+1F<2~u zWxi*eix&sa{XF0CGFYn{dnSDvoHgW8glBwCV`jQfhu^W{=L8Wpng_C%Bne@z2vLw( zJD>T10p!0~yR%8R#u+2q*`zDuj+gCh(zS7cygYRJD)UZz*=Y;085Dpd1H%reJeUI|{{R2a0K!0E0K~OG4C4F&;(Q?1 zg2|)PK)H`Vd<|V3oexy=0Eo|_i=*?0syd*%Es*_!%^yH98z5c`#EdYt=<0xc79d^; b#E*dZHV|(C;#ENW6^P$M*&wl}K>P#%{8sQ? literal 0 HcmV?d00001 diff --git a/bmps/times-new-roman/U0.bmp b/bmps/times-new-roman/U0.bmp new file mode 100644 index 0000000000000000000000000000000000000000..5e0358d9e359951c2f2941b462ddfc08b3fb0cf6 GIT binary patch literal 630 zcmdUtu?_)25Qg{EI)zs02}D#EK}AP`xQ2v+T12T5@kBHVtwifB)SC1Cmz`wd4NUUw z%zQVO-A%SV*p@;O4c0o8BT1-Hg^`-3Rw*%gO$aH%qGMG`K5}F(g!pmr^aWkAk_vFjOwAs<3o@T&BB*CwOrk@pQ8A zHVnkaO?nduVF<^#o_IPpxI+rIP?nuL*>yqx?du(<34z;_%x(`K$XV`0PD_@FE5H{v z;`HSB(6^oO0bUpC_Kuu~gX28h1Fp80H+~02E04(f@3}a8;>WPkjx&W9=x~9y)2-po H@Pox4*Y_lq literal 0 HcmV?d00001 diff --git a/bmps/times-new-roman/W0.bmp b/bmps/times-new-roman/W0.bmp new file mode 100644 index 0000000000000000000000000000000000000000..dabd3c1d4886b395e7242b4f175437f19a926ce6 GIT binary patch literal 822 zcmZvYAxJ|}6o%il7(_Ni7Bz`57!&mcK^THjFl&*qHWW+?rj1=&OsK&ArsAT5@~-R24|8akq37I zPQ0g@Xh0$KL(R-Ke88CRp`W4Qy^ zA7*;Hpu1P#2K4uhKp*a537X-aX?hRv3fif{CX_+ntZ9zuLdJG1<4uF!*iPqi)K_o@ z`c3)NKcHT)JU8CQj>%4jNp=rx_kMIKjZ=>D;eY0Y7%ETS`xguJPRxsDW z)7x`_-@;oqkNOC-Uv}Ok`V)HQS;kv~Q)yxkG$(NkQ&0lEQQjMDx!qaxIebC=7mQ6z AxBvhE literal 0 HcmV?d00001 diff --git a/bmps/times-new-roman/X1.bmp b/bmps/times-new-roman/X1.bmp new file mode 100644 index 0000000000000000000000000000000000000000..0f36ca71a3f0bd07817d5f4f7380df26d0f15b53 GIT binary patch literal 582 zcmZwEJqtlm7{KvEOx{zhHVg(SJLzViEcZiL_yA_JN!gT9QYKPHU%@OCyG^+M&+Xh? zC#QbrJa6}QU+QfusN7X$FCdBt0xHL@R8sfdjT`d+>QVM literal 0 HcmV?d00001 diff --git a/bmps/times-new-roman/Y1.bmp b/bmps/times-new-roman/Y1.bmp new file mode 100644 index 0000000000000000000000000000000000000000..6bee7e6431a032cbbc650be44ace497c7602faeb GIT binary patch literal 582 zcmb8pp$o!56vy#XL=X)oiv|${lbDPK!`GNlKP}9rwN@FTHbQ*y#uBZp?|U+H zL*(nbIkOj>+}v0HGtX=R@;|ZfeRj^=8X8AT?Vfw4-@^-g$afB?xpPvpiiq$)!(?5~ sJJ0-;B4n|W|fejZt4~1JER$7V&9>jYQRN0MLr~Tye-;R_Pdn`R0?k|iXuY5x0^VVA9 zBCB8_8LI~=Chm;ZKsAtIPT~1aLA8e6HvmNyXE4cs3mAgDN`~iQZ!t4M5 literal 0 HcmV?d00001 diff --git a/bmps/times-new-roman/a4.bmp b/bmps/times-new-roman/a4.bmp new file mode 100644 index 0000000000000000000000000000000000000000..6ce75a20aea552487332a88302c61b56898d92a8 GIT binary patch literal 362 zcmZ?r&0=H#gDxOh1H|k=%mKuV37g?ui=GYAQKAyGcZ69i2vWoJ*c?h&{vIw z9de=yt*;p!jE#jG($dr%($d%#%$&(-!*!imC8pm9rW-`FAafw>cp&u-h_3^&FA&!O z@qHk62jbU2yd8+SfY=9!?*j2LAO^|Z0^+*=|Nk?9Fc547;tf!?6Oer#h;@R literal 0 HcmV?d00001 diff --git a/bmps/times-new-roman/b0.bmp b/bmps/times-new-roman/b0.bmp new file mode 100644 index 0000000000000000000000000000000000000000..7aa6910a268f25c3945a3e4416407ef396b4fe4c GIT binary patch literal 522 zcmZ?rFa%mc)X3n;0=8%@gwqWK=P8+W4%qlVcMljtVngy8yWdHvUG?0No1BjOZ z@lhb20>qPnSP~=$mEQv79|K|zC|?uEegMR>Nb;*;>Y-vF^A_NduLa7#z$4!dl>drN z9w-hB9fq?&d7d>ui=GYAQKAyGcZ69i2vWoJ*c?h&{vIw z9de=yt*;p!jE#jG($dr%($d%#%$&(-!*!imC8pm9rW-`FAaj81|NnsoGB9KU@dF@! z3dBWFu^=EDB)J8MBY=1^5XS)VQ6N4K#4I3nP_^km{yQMfgNZ@Kd4OWCf!G^a94fa5 nC>{$F2eKW2*d2&%fOs1aGeXs!1+q6l*&z8|Af64xApPFa%mc)X3n;0=8%@gwqWK=P8+W4%qlVcMljtVngy8yWdHvUG?0NI6^QQu z@e?2hIcYf%uLELNAPxs&kfNDD%mu_UKztd9?*g$Z5T60!2SCgQG7DHgV zUI3FrlHUbo-vi3`VUgbnm7fKaUxOyU1}FxTmj<$50I?iYJwK5B5+)B7GY9fPdOCo3 r0}x*UVz7QRbD5y(mtm8KsRhcd#U&3ER{-L(c;sz?a-i_t3^ES@DJ8Ij literal 0 HcmV?d00001 diff --git a/bmps/times-new-roman/e4.bmp b/bmps/times-new-roman/e4.bmp new file mode 100644 index 0000000000000000000000000000000000000000..ec694144d875372355f4b6a8f4033f8409a20726 GIT binary patch literal 362 zcmZ?r&0=H#gDxOh1H|k=%mKuV37g?ui=GYAQKAyGcZ69i2vWoJ*c?h&{vIw z9de=yt*;p!jE#jG($dr%($d%#%$&(-!*!imC8pm9rW-`FAaj81|NnsoGB7v*@lhcD z48&J}I21LB!L4Dv$<5VHdS Dli5x4 literal 0 HcmV?d00001 diff --git a/bmps/times-new-roman/f0.bmp b/bmps/times-new-roman/f0.bmp new file mode 100644 index 0000000000000000000000000000000000000000..c305bf7706538d6a8a45f803246a1e8bd36f8bb6 GIT binary patch literal 474 zcmZ?ry~W4?23+ehn`K1G!M}pMe2_K>Yts?m@*3hrVhg z?2r>xXnoD-U~DYhkd~(Ake0@_VCGCt8?NijDlz>=Fx?=U1(^e3M*^ukKnygQp#zAW z{{R2a0K-6j3lRSWVrG~aj0TF81MycFADO179MGf~}v*!Q+ literal 0 HcmV?d00001 diff --git a/bmps/times-new-roman/g4.bmp b/bmps/times-new-roman/g4.bmp new file mode 100644 index 0000000000000000000000000000000000000000..91e8dcb47037f509348975e1d3ec2cc63dd655a1 GIT binary patch literal 522 zcmYk(JxIeq6u|KpE7AsasV){uhf*z08gX(cL1;lbSa5T2$|4lRK?jE{E`sQwU_nbq zK}QwmQqbAOS=7nu=AuLTf6WE%$S?Qay&U%uzwVC6upRL}KpNw4q#!B!&=I+vh>@BW ziNufQkzZ}>hTY$}t;hUy;qc3;cwROPf>I?2#yYK5X2pF?6*o^7liAvj+DROj=-PP0 zA3o4QF^X7BE=SAQ_t_zQi`JagIGS2hyT=$p8QV literal 0 HcmV?d00001 diff --git a/bmps/times-new-roman/h0.bmp b/bmps/times-new-roman/h0.bmp new file mode 100644 index 0000000000000000000000000000000000000000..c34b689818920622eb9a00e4d0ad5bbab987e8f2 GIT binary patch literal 522 zcmZ?rFa%mc)X3n;0=8%@gwqWK=P8+W4%qlVcMljtVngy8yVaEWeFF<@9h~0qL z42TZ{G0-N4{Qv(!6vHVXo(5t=`FTM82P8g7{RAX_4v_x>iBGCL$o!{h`llfA8-Vgx zkoX|;j{|W65Gw=m2_W8rCO-iv<^aU!f%qm68zadd0rKJQhl#^zpxkL(@<4GdAby5N P-W4df8HoE}<{{Gn_PM@7 literal 0 HcmV?d00001 diff --git a/bmps/times-new-roman/i0.bmp b/bmps/times-new-roman/i0.bmp new file mode 100644 index 0000000000000000000000000000000000000000..1060f093aba2f57bfe6e6b7958daa9a747582474 GIT binary patch literal 330 zcmZ?r^#q%mc)X37d>ui=GYAQKAyGcZ69i2vWoJ*c?h&{vIw z9de=yt*;p!jE#jG($dr%($d%#%$&(-!*!imC8pm9rW-_q>_Gz|K>lkW{tm?H|NjFm zU|`q?#1}zqD1SMSe-aOW1)BUO7#~QR0r7PtzA2DB6NtCL%p*ht)y4sF2}~VS|8@`` E0EstkXaE2J literal 0 HcmV?d00001 diff --git a/bmps/times-new-roman/j2.bmp b/bmps/times-new-roman/j2.bmp new file mode 100644 index 0000000000000000000000000000000000000000..4ad08f81f3632b51709e78bde659ddb7a1f25825 GIT binary patch literal 458 zcmZ?rJ;lfX23Yts?m@*3hrVhg z?2r>xXnoD-U~DYhkd~(Ake0@_VCGCt8?NijDlz>=Fx?=U1(^e3Zw6A|fOtL-hyDK# zw19yj4~Si%>_b4d2Z#;BK>jKqUJBzw)hz;woxmb?gNkB{fp(yqw+>kh$o2)|+h}5n bKz>EIQ7l_3{Vo>qrK>j%(2KjCa5Wfdv4VXNd znhv1cA0YNdmPb+pGXEM7H=)ZTsR7v~2*hiF*a}@8Pz>xIsCsrFe+3YSA*(}H165Z6 s6x$8N^+0R@6$9xzhpZ2s4U}JxPYqB`9f&UwP~!lUUjoF((9MVO0XhBBegFUf literal 0 HcmV?d00001 diff --git a/bmps/times-new-roman/l0.bmp b/bmps/times-new-roman/l0.bmp new file mode 100644 index 0000000000000000000000000000000000000000..a41c814fb778dc29e7cd97c7ee1cfb5cdc5a1fe9 GIT binary patch literal 330 zcmZ?r^#q%mc)X37d>ui=GYAQKAyGcZ69i2vWoJ*c?h&{vIw z9de=yt*;p!jE#jG($dr%($d%#%$&(-!*!imC8pm9rW-_q>_G#;K>j-*{s_d$|NjFm sU|`q@#D_p^D1Rf6zl(<8Niq&0x}IuhRz$X)@&TR`ps02rHO)Bpeg literal 0 HcmV?d00001 diff --git a/bmps/times-new-roman/m4.bmp b/bmps/times-new-roman/m4.bmp new file mode 100644 index 0000000000000000000000000000000000000000..b69c930670be6fbc27dd930696d721a779c73c81 GIT binary patch literal 554 zcmZ?r)nZ}*gDxOh1H`;Q%mKuV3e0 z@nxWToRGv?fnv9i_~hu>NrpWKfqIrA*{h1hZM{ITb09T9pbNwgfcPvBCjc=&5H|wx zKOmj}#8yBo1;m$ucqLSi6Og?Ih!+5{IS`)#;?F?b0K}|7+ylg*xVQ<#+8{juax$Ks literal 0 HcmV?d00001 diff --git a/bmps/times-new-roman/n4.bmp b/bmps/times-new-roman/n4.bmp new file mode 100644 index 0000000000000000000000000000000000000000..454b97781346c04454a9cc3764c2b875d6a7e547 GIT binary patch literal 394 zcmZ?r?P6pAgDxOh1H>Fq%*dd?zzmWB%CF&tU=SAq{xdK@5QzKV$vvpJ;m}u&gdK9C z3azgh9gK~I8`9F$9MaO*7R;Q@Xnp6Ns+@u`>{x1Mx8+ z{s+Xl|NjFmWMH@i#1lbmD8CTMe~H9D3gmYo@$-TFhe&)v@*w;0=|2mU??p1N3dp|- p;sb#$5WfIokR7o=EDdFEg37x9`I~^a1Bm^A_z4g{24XXiJOG{eaV-D< literal 0 HcmV?d00001 diff --git a/bmps/times-new-roman/o4.bmp b/bmps/times-new-roman/o4.bmp new file mode 100644 index 0000000000000000000000000000000000000000..4c16844d89f4e6cc7e482332e0b485dbf01ff072 GIT binary patch literal 362 zcmZ?r&0=H#gDxOh1H|k=%mKuV37g?ui=GYAQKAyGcZ69i2vWoJ*c?h&{vIw z9de=yt*;p!jE#jG($dr%($d%#%$&(-!*!imC8pm9rW-`FAaj81|NnsoGBAVy@p&Mg z2gI|1*a0eT3uONR;xH&5Fa%mc)X3n;0=8%@gwqWK=P8+W4%qlVcMljtVngy8yVW$GA*FgLph|~Z7 z|IdJqf#O?%cn`Wbj1QDs1H@Zkd~_Nn52Ve2I0=X?kkvrtKLY7&AXWn61|Xgb#7rP{ zKmaoTG@73CK(S;b`R!=(lYsKakmOO^2b608;?H>G7XjtpL*>^4*(ZP)<{nT~y@iTd r0O{*Md=`j7ZVm?GZ9oiicnlD01MzeqUJS&xKztL3UjcC=5HkV*c8IBW literal 0 HcmV?d00001 diff --git a/bmps/times-new-roman/q4.bmp b/bmps/times-new-roman/q4.bmp new file mode 100644 index 0000000000000000000000000000000000000000..5f79f692a0d53fb02e0c7828a0639fafeda5983d GIT binary patch literal 522 zcmZ?rFa%mc)X3n;0=8%@gwqWK=P8+W4%qlVcMljtVngy8yWdHyF9|Z%2LV)-w z5Pt^ZbaZ(T9|%?e@d<2lQ27Nw`2#R{Ae#?}!-3crh(X?40K{NPwUQn}zf$R%N^7n!4)iArz?FY*J1ma3O@{fV?C3xgP_RmI>Uj`I| lxew%ikh|5Oc5?#Rw_x&6F-0JMJ`gVi;zK|@9f&u87d>ui=GYAQKAyGcZ69i2vWoJ*c?h&{vIw z9de=yt*;p!jE#jG($dr%($d%#%$&(-!*!imC8pm9rW-`FAafw>SRnNRi2nj{Ef5R+ z|Nox>gn?iM5Z{6EVKh)|2@s!!@nJL`@ufJ_uK|kR1!86-GjxIc3qbq;h>L(Y6No{< TWCz5HfcOXyTLbY^AU*&9n0aFc literal 0 HcmV?d00001 diff --git a/bmps/times-new-roman/s4.bmp b/bmps/times-new-roman/s4.bmp new file mode 100644 index 0000000000000000000000000000000000000000..5eccdbce5cda32419af96d9245614a131f34b5ea GIT binary patch literal 298 zcmZ?r)na4-gDxOh1H`OA%mKuV3{Xl935Z40nEFjhd z;uAp3@&7;2G6n{FAU+MmflxlksndY?6O?ZWWM2Sckm7bAE(PL$Kzs*?qk$OY(@#L$ g2gIyUJ?236bs&b>VGCpz12KpOsROxXJrL&r0OZv+J^%m! literal 0 HcmV?d00001 diff --git a/bmps/times-new-roman/t2.bmp b/bmps/times-new-roman/t2.bmp new file mode 100644 index 0000000000000000000000000000000000000000..4db29b7f0233f81707ac86c8a5d4e74047765dd2 GIT binary patch literal 338 zcmZ?r4Ps;fgDxOh1H`OA%mu`Z37p_ui=GYAQKAyGcZ69i2vWoJ*c?h&{vIw z9de=yt*;p!jE#jG($dr%($d%#%$&(-!*!imC8pm9rW-`FAaj81|NnsoGB8*H@e?4v z3B-0#vDrZO3n1nLVh^a;dLa88hz-L)KGnp4X7vK`A0P(V+X2L>PY!rK;;+-&e F003HsZm9qO literal 0 HcmV?d00001 diff --git a/bmps/times-new-roman/u4.bmp b/bmps/times-new-roman/u4.bmp new file mode 100644 index 0000000000000000000000000000000000000000..5c54ad1ef2b0f0fe1348b44a3e93b61aba9a4290 GIT binary patch literal 426 zcmZ?rUB$=%23Yts?m@*3hrVhg z?2r>xXnoD-U~DYhkd~(Ake0@_VCGCt8?NijDlz>=Fx?=U1(^e6|Njp(kb!{@h<5|= zCm`Mc#3Dd^0*Ds@u`N_h9+3S9h~0r$8Hhp7ehS3rfmj==W+IS%55$IHApZ&wuSVi8 z1oE%K_#hexE&%Zom>7_aOU+^u)hq{^g>KhnpcvR3Ae{)r&tYnSG#3zO1MyEF2CD%8 D#bk%m literal 0 HcmV?d00001 diff --git a/bmps/times-new-roman/v4.bmp b/bmps/times-new-roman/v4.bmp new file mode 100644 index 0000000000000000000000000000000000000000..288869cad6b699ffa056bafde87449fd7306c21a GIT binary patch literal 394 zcmZ?r?P6pAgDxOh1H>Fq%*dd?zzmWB%CF&tU=SAq{xdK@5QzKV$vvpJ;m}u&gdK9C z3azgh9gK~I8`9F$9MaO*7R;QYlFEMNDXa*g401$L#xC=wCLdEAQT7LC|0O;cD^O#3t6^C{HDbfuq!Oa*5I` zt<>_8T9?Y_SHW<8(0EJlc(>n9u~66!d_Pm4ba;Rkcn%Fq%*dd?zzmWB%CF&tU=SAq{xdK@5QzKV$vvpJ;m}u&gdK9C z3azgh9gK~I8`9F$9MaO*7R;Q|P-C28d1n{|6e*z+eHy zAA$HU5Z6J)<^tJTP__Y7t{%jPig5$QmI5(55FY{Jb3n`rlZUD?28x{k;-5gQgDeM? zR{)BC0pflva-h)K1;jUjcqI@EAgclLbATA+cyA!q0phhl%mg*J8p!?y#327z0Y;0859_pfph{;ehn`K1G!M}pMe2_K>Yts?m@*3hrVhg z?2r>xXnoD-U~DYhkd~(Ake0@_VCGCt8?NijDlz>=Fx?=U1(^e3X9B6uKs*I07cY&A*iySaS7%l{1Kq|B9J%`%m-pAm>MA44v7B&u?|$;7RatflHUd7?}G86 aYCvM3sOke^kfBF`SOqGl2xOlF;t2qK8nO-m literal 0 HcmV?d00001 diff --git a/bmps/times-new-roman/z4.bmp b/bmps/times-new-roman/z4.bmp new file mode 100644 index 0000000000000000000000000000000000000000..959bbbe6501c1c8c2b3bbb476cf8e20ac9db8fe4 GIT binary patch literal 362 zcmZ?r&0=H#gDxOh1H|k=%mKuV37g?ui=GYAQKAyGcZ69i2vWoJ*c?h&{vIw z9de=yt*;p!jE#jG($dr%($d%#%$&(-!*!imC8pm9rW-`FAafw>D?sWG5YGkTnNYk1 zNS_2^T_FAr#LfTz|7U<r|P@~k<(X?aZm literal 0 HcmV?d00001 diff --git a/bmps/times-new-roman/█1.bmp b/bmps/times-new-roman/█1.bmp new file mode 100644 index 0000000000000000000000000000000000000000..fd27b3405277f4fd8edf59a7c11af1d85042f95d GIT binary patch literal 618 zcmZ?r&0=BzgDxOh1H>Fa%n!tj3 { + name_: String, + top_left: Point, + size: Dimensions, + text: &'static str, + pub highlighted: bool, + click_return: T, + toggle_highlight_return: T, //also unhighlight return +} + +impl Component for HighlightButton { + fn handle_message(&mut self, message: WindowMessage) -> Option { + match message { + WindowMessage::Focus | WindowMessage::Unfocus => { + self.highlighted = !self.highlighted; + Some(self.toggle_highlight_return.clone()) + }, + WindowMessage::FocusClick => { + //we know this click was for this button, otherwise window wouldn't have given us this message + Some(self.click_return.clone()) + }, + _ => None, + } + } + + fn draw(&self, theme_info: &ThemeInfo) -> Vec { + let font_height = 15; + if self.highlighted { + vec![ + //highlight background + DrawInstructions::Rect(self.top_left, self.size, theme_info.top), + DrawInstructions::Text([self.top_left[0] + 4, self.top_left[1] + (self.size[1] - font_height) / 2], "times-new-roman", self.text.to_string(), theme_info.text_top, theme_info.top, None), + ] + } else { + vec![ + DrawInstructions::Rect(self.top_left, self.size, theme_info.background), + DrawInstructions::Text([self.top_left[0] + 4, self.top_left[1] + (self.size[1] - font_height) / 2], "times-new-roman", self.text.to_string(), theme_info.text, theme_info.background, None), + ] + } + } + + //properties + fn focusable(&self) -> bool { + true + } + + fn clickable(&self) -> bool { + true + } + + fn name(&self) -> &String { + &self.name_ + } +} + +impl HighlightButton { + pub fn new(name_: String, top_left: Point, size: Dimensions, text: &'static str, click_return: T, toggle_highlight_return: T, highlighted: bool) -> Self { + Self { + name_, + top_left, + size, + text, + click_return, + toggle_highlight_return, + highlighted, + } + } +} + diff --git a/src/components/mod.rs b/src/components/mod.rs new file mode 100644 index 0000000..ed15e7c --- /dev/null +++ b/src/components/mod.rs @@ -0,0 +1,21 @@ +use std::vec::Vec; + +use crate::themes::ThemeInfo; +use crate::messages::WindowMessage; +use crate::window_manager::DrawInstructions; + +pub mod toggle_button; +pub mod highlight_button; + +pub trait Component { + fn handle_message(&mut self, message: WindowMessage) -> Option; + fn draw(&self, theme_info: &ThemeInfo) -> Vec; + + //properties + //focusing is a way for the *window* to know what component to send input, presses, etc + //focusing for components is purely to give a visual representation + fn focusable(&self) -> bool; + fn clickable(&self) -> bool; + fn name(&self) -> &String; //should be unique +} + diff --git a/src/components/toggle_button.rs b/src/components/toggle_button.rs new file mode 100644 index 0000000..5368df2 --- /dev/null +++ b/src/components/toggle_button.rs @@ -0,0 +1,90 @@ +use std::vec; +use std::vec::Vec; + +use crate::components::Component; +use crate::framebuffer::{ Dimensions, Point }; +use crate::themes::ThemeInfo; +use crate::messages::WindowMessage; +use crate::window_manager::DrawInstructions; + +//we need a text width and height measure function first +pub enum ToggleButtonAlignment { + Centre, + Left, +} + +pub struct ToggleButton { + name_: String, + top_left: Point, + size: Dimensions, + text: &'static str, + draw_bg: bool, + pub inverted: bool, //whether is it clicked or not + alignment: ToggleButtonAlignment, + click_return: T, + unclick_return: T, +} + +impl Component for ToggleButton { + fn handle_message(&mut self, message: WindowMessage) -> Option { + match message { + WindowMessage::FocusClick => { + //we know this click was for this button, otherwise window wouldn't have given us this message + self.inverted = !self.inverted; + if self.inverted { + Some(self.click_return.clone()) + } else { + Some(self.unclick_return.clone()) + } + }, + _ => None, + } + } + + fn draw(&self, theme_info: &ThemeInfo) -> Vec { + //to make sure the text gets vertically centred + let font_height = 15; + vec![ + //top and left border + DrawInstructions::Rect(self.top_left, [self.size[0], 2], if self.inverted { theme_info.border_right_bottom } else { theme_info.border_left_top }), + DrawInstructions::Rect(self.top_left, [2, self.size[1]], if self.inverted { theme_info.border_right_bottom } else { theme_info.border_left_top }), + //right and bottom border + DrawInstructions::Rect([self.top_left[0] + self.size[0] - 2, self.top_left[1]], [2, self.size[1]], if self.inverted { theme_info.border_left_top } else { theme_info.border_right_bottom }), + DrawInstructions::Rect([self.top_left[0], self.top_left[1] + self.size[1] - 2], [self.size[0], 2], if self.inverted { theme_info.border_left_top } else { theme_info.border_right_bottom }), + //the background if self.draw_bg + //DrawInstructions::Rect(), + //the text (for now, hardcoded top left) + DrawInstructions::Text([self.top_left[0] + 4, self.top_left[1] + (self.size[1] - font_height) / 2], "times-new-roman", self.text.to_string(), theme_info.text, theme_info.background, None), + ] + } + + //properties + fn focusable(&self) -> bool { + true + } + + fn clickable(&self) -> bool { + true + } + + fn name(&self) -> &String { + &self.name_ + } +} + +impl ToggleButton { + pub fn new(name_: String, top_left: Point, size: Dimensions, text: &'static str, click_return: T, unclick_return: T, draw_bg: bool, alignment: Option) -> Self { + Self { + name_, + top_left, + size, + text, + click_return, + unclick_return, + draw_bg, + inverted: false, + alignment: alignment.unwrap_or(ToggleButtonAlignment::Centre), + } + } +} + diff --git a/src/framebuffer.rs b/src/framebuffer.rs new file mode 100644 index 0000000..31872a4 --- /dev/null +++ b/src/framebuffer.rs @@ -0,0 +1,228 @@ +use std::vec::Vec; +use core::ptr; + +use crate::fs::{ get_font_char, get_bmp }; + +pub type Point = [usize; 2]; //x, y +pub type Dimensions = [usize; 2]; //width, height +pub type RGBColor = [u8; 3]; //rgb + +type FontChar = (char, Vec>, u8); + +fn color_with_alpha(color: RGBColor, bg_color: RGBColor, alpha: u8) -> RGBColor { + /*let factor: f32 = alpha as f32 / 255.0; + [ + (bg_color[0] as f32 * (1.0 - factor)) as u8 + (color[0] as f32 * factor) as u8, + (bg_color[1] as f32 * (1.0 - factor)) as u8 + (color[1] as f32 * factor) as u8, + (bg_color[2] as f32 * (1.0 - factor)) as u8 + (color[2] as f32 * factor) as u8, + ]*/ + //255 * 255 < max(u16) + let alpha = alpha as u16; + [ + (bg_color[0] as u16 * (255 - alpha) / 255) as u8 + (color[0] as u16 * alpha / 255) as u8, + (bg_color[1] as u16 * (255 - alpha) / 255) as u8 + (color[1] as u16 * alpha / 255) as u8, + (bg_color[2] as u16 * (255 - alpha) / 255) as u8 + (color[2] as u16 * alpha / 255) as u8, + ] +} + +#[derive(Clone, Default, Debug)] +pub struct FramebufferInfo { + pub byte_len: usize, + pub width: usize, + pub height: usize, + pub bytes_per_pixel: usize, + pub stride: usize, +} + +//currently doesn't check if writing onto next line accidentally +pub struct FramebufferWriter { + info: FramebufferInfo, + buffer: Vec, + saved_buffer: Option>, +} + +impl FramebufferWriter { + pub fn init(&mut self, info: FramebufferInfo, buffer: Vec) { + self.info = info; + self.buffer = buffer; + } + + pub fn get_info(&self) -> FramebufferInfo { + self.info.clone() + } + + pub fn get_buffer(&self) -> &[u8] { + &self.buffer + } + + pub fn save_buffer(&mut self) { + self.saved_buffer = Some(self.buffer.clone()); + } + + pub fn write_saved_buffer_to_raw(&mut self) { + self.buffer[..] + .copy_from_slice(&self.saved_buffer.as_ref().unwrap()[..]); + } + + fn _draw_pixel(&mut self, start_pos: usize, color: RGBColor) { + self.buffer[start_pos..(start_pos + 3)] + .copy_from_slice(&color[..]); + } + + fn _draw_line(&mut self, start_pos: usize, bytes: &[u8]) { + self.buffer[start_pos..(start_pos + bytes.len())] + .copy_from_slice(bytes); + } + + pub fn draw_buffer(&mut self, top_left: Point, height: usize, bytes_per_line: usize, bytes: &[u8]) { + //for our framebuffer + let mut start_pos = (top_left[1] * self.info.stride + top_left[0]) * self.info.bytes_per_pixel; + //of the buffer we want to draw on + let mut start = 0; + for _y in 0..height { + self.buffer[start_pos..(start_pos + bytes_per_line)] + .copy_from_slice(&bytes[start..(start + bytes_per_line)]); + let _ = unsafe { ptr::read_volatile(&self.buffer[start_pos]) }; + start += bytes_per_line; + start_pos += self.info.stride * self.info.bytes_per_pixel; + } + } + + pub fn draw_char(&mut self, top_left: Point, char_info: &FontChar, color: RGBColor, bg_color: RGBColor) { + let mut start_pos; + for row in 0..char_info.1.len() { + //char_info.2 is vertical offset + start_pos = ((top_left[1] + row + char_info.2 as usize) * self.info.stride + top_left[0]) * self.info.bytes_per_pixel; + for col in &char_info.1[row] { + if col > &0 { + self._draw_pixel(start_pos, color_with_alpha(color, bg_color, *col)); + } + start_pos += self.info.bytes_per_pixel; + } + } + } + + //dots + + pub fn draw_pixel(&mut self, point: Point, color: RGBColor) { + let start_pos = (point[1] * self.info.stride + point[0]) * self.info.bytes_per_pixel; + self._draw_pixel(start_pos, color); + } + + //(lines are rectangles of height 1) + pub fn draw_line(&mut self, left: Point, width: usize, color: RGBColor) { + self.draw_rect(left, [width, 1], color); + } + + //shapes + + pub fn draw_rect(&mut self, top_left: Point, dimensions: Dimensions, color: RGBColor) { + let line_bytes = if self.info.bytes_per_pixel > 3 { + [color[0], color[1], color[2], 255].repeat(dimensions[0]) + } else { + color.repeat(dimensions[0]) + }; + let mut start_pos = (top_left[1] * self.info.stride + top_left[0]) * self.info.bytes_per_pixel; + for _row in 0..dimensions[1] { + /* + * for _col in 0..dimensions[0] { + self._draw_pixel(start_pos, color); + start_pos += self.info.bytes_per_pixel; + } + //assumes stride is same as bytes_per_pixel * width + //start_pos = start_pos + top_left[0] * self.info.bytes_per_pixel; + */ + //use _draw_line instead for MUCH more efficiency + self._draw_line(start_pos, &line_bytes[..]); + start_pos += self.info.stride * self.info.bytes_per_pixel; + } + } + + //direction is top to bottom + pub fn draw_gradient(&mut self, top_left: Point, dimensions: Dimensions, start_color: RGBColor, end_color: RGBColor, steps: usize) { + let delta_r = (end_color[0] as f32 - start_color[0] as f32) / steps as f32; + let delta_g = (end_color[1] as f32 - start_color[1] as f32) / steps as f32; + let delta_b = (end_color[2] as f32 - start_color[2] as f32) / steps as f32; + let mut start_pos = (top_left[1] * self.info.stride + top_left[0]) * self.info.bytes_per_pixel; + if steps <= dimensions[1] { + //rounds down + let mut y_per = dimensions[1] / steps; + for s in 0..steps { + let color; + if s == steps - 1 { + color = end_color; + //the remaining lines are the last one + y_per = dimensions[1] - (y_per * steps); + } else { + color = [(start_color[0] as f32 + (delta_r * s as f32)) as u8, (start_color[1] as f32 + (delta_g * s as f32)) as u8, (start_color[2] as f32 + (delta_b * s as f32)) as u8]; + }; + let line_bytes = if self.info.bytes_per_pixel > 3 { + [color[0], color[1], color[2], 255].repeat(dimensions[0]) + } else { + color.repeat(dimensions[0]) + }; + for _y in 0..y_per { + self._draw_line(start_pos, &line_bytes[..]); + start_pos += self.info.stride * self.info.bytes_per_pixel; + } + } + } + } + + //text + + pub fn draw_text(&mut self, top_left: Point, font_name: &str, text: &str, color: RGBColor, bg_color: RGBColor, horiz_spacing: usize, mono_width: Option) { + let mut top_left = top_left; + //todo, config space + for c in text.chars() { + if c == ' ' { + top_left[0] += 5; + } else { + let char_info = get_font_char(&("./bmps/".to_string() + font_name), c); + if let Some(char_info) = char_info { + let char_width = char_info.1[0].len(); + let add_after: usize; + if let Some(mono_width) = mono_width { + let mono_width = mono_width as usize; + let remainder = if mono_width < char_width { + 0 + } else { + mono_width - char_width + }; + top_left[0] += remainder / 2; + add_after = remainder - remainder / 2 + char_width; + } else { + add_after = char_width + horiz_spacing; + } + self.draw_char(top_left, &char_info, color, bg_color); + top_left[0] += add_after; + } + } + } + } + + //bmps + + pub fn _draw_mingde(&mut self, top_left: Point) { + let mut start_pos; + let mingde = get_bmp("./bmps/mingde.bmp"); + for row in 0..mingde.len() { + start_pos = ((top_left[1] + row) * self.info.stride + top_left[0]) * self.info.bytes_per_pixel; + for color in &mingde[row] { + self._draw_pixel(start_pos, [color[0], color[1], color[2]]); + start_pos += self.info.bytes_per_pixel; + } + } + } +} + +impl Default for FramebufferWriter { + fn default() -> Self { + Self { + info: Default::default(), + buffer: Vec::new(), + saved_buffer: None, + } + } +} + diff --git a/src/fs.rs b/src/fs.rs new file mode 100644 index 0000000..6f3ae4a --- /dev/null +++ b/src/fs.rs @@ -0,0 +1,52 @@ +use std::fs::read_dir; +use std::path::Path; + +use bmp_rust::bmp::BMP; + +pub fn get_font_char(dir: &str, c: char) -> Option<(char, Vec>, u8)> { + let mut font: Vec<(char, Vec>, u8)> = Vec::new(); + for entry in read_dir(dir).unwrap() { + let path = entry.unwrap().path(); + let path_chars: Vec = path.file_name().unwrap().to_str().unwrap().to_string().chars().collect(); + if path_chars[0] == c { + let mut ch: Vec> = Vec::new(); + if !path.is_dir() { + let b = BMP::new_from_file(&path.clone().into_os_string().into_string().unwrap()); + let dib_header = b.get_dib_header().unwrap(); + let width = dib_header.width as usize; + let height = dib_header.height as usize; + for y in 0..height { + let mut row = Vec::new(); + for x in 0..width { + let pixel_color = b.get_color_of_px(x, y).unwrap(); + //if black, true + row.push(pixel_color[3]); //push alpha channel + } + ch.push(row); + } + return Some((path_chars[0], ch, path_chars[1].to_digit(10).unwrap() as u8)); + } + } + } + None +} + +//the Vec should be [u8; 3] but thats a job for another day +pub fn get_bmp(path: &str) -> Vec>> { + let mut bmp: Vec>> = Vec::new(); + let b = BMP::new_from_file(path); + let dib_header = b.get_dib_header().unwrap(); + let width = dib_header.width as usize; + let height = dib_header.height as usize; + for y in 0..height { + let mut row = Vec::new(); + for x in 0..width { + let pixel_color = b.get_color_of_px(x, y).unwrap(); + //if black, true + row.push(vec![pixel_color[0], pixel_color[1], pixel_color[2]]); //push alpha channel + } + bmp.push(row); + } + bmp +} + diff --git a/src/keyboard.rs b/src/keyboard.rs new file mode 100644 index 0000000..0152a01 --- /dev/null +++ b/src/keyboard.rs @@ -0,0 +1,109 @@ + +#[derive(Clone, Debug)] +pub enum KeyChar { + Press(char), + SpecialPress(&'static str), + SpecialRelease(&'static str), +} + +//use Linear A for escape, backspace, enter +//https://wiki.osdev.org/PS/2_Keyboard#Scan_Code_Set_1 +pub fn scancode_to_char(scancode: u8) -> Option { + match scancode { + 0x01 => Some(KeyChar::Press('𐘀')), //escape + 0x02 => Some(KeyChar::Press('1')), + 0x03 => Some(KeyChar::Press('2')), + 0x04 => Some(KeyChar::Press('3')), + 0x05 => Some(KeyChar::Press('4')), + 0x06 => Some(KeyChar::Press('5')), + 0x07 => Some(KeyChar::Press('6')), + 0x08 => Some(KeyChar::Press('7')), + 0x09 => Some(KeyChar::Press('8')), + 0x0A => Some(KeyChar::Press('9')), + 0x0B => Some(KeyChar::Press('0')), + 0x0C => Some(KeyChar::Press('-')), + 0x0D => Some(KeyChar::Press('=')), + 0x0E => Some(KeyChar::Press('𐘁')), //backspace + // + 0x10 => Some(KeyChar::Press('q')), + 0x11 => Some(KeyChar::Press('w')), + 0x12 => Some(KeyChar::Press('e')), + 0x13 => Some(KeyChar::Press('r')), + 0x14 => Some(KeyChar::Press('t')), + 0x15 => Some(KeyChar::Press('y')), + 0x16 => Some(KeyChar::Press('u')), + 0x17 => Some(KeyChar::Press('i')), + 0x18 => Some(KeyChar::Press('o')), + 0x19 => Some(KeyChar::Press('p')), + 0x1A => Some(KeyChar::Press('[')), + 0x1B => Some(KeyChar::Press(']')), + 0x1C => Some(KeyChar::Press('𐘂')), //enter + // + 0x1E => Some(KeyChar::Press('a')), + 0x1F => Some(KeyChar::Press('s')), + 0x20 => Some(KeyChar::Press('d')), + 0x21 => Some(KeyChar::Press('f')), + 0x22 => Some(KeyChar::Press('g')), + 0x23 => Some(KeyChar::Press('h')), + 0x24 => Some(KeyChar::Press('j')), + 0x25 => Some(KeyChar::Press('k')), + 0x26 => Some(KeyChar::Press('l')), + 0x27 => Some(KeyChar::Press(';')), + 0x28 => Some(KeyChar::Press('\'')), + 0x29 => Some(KeyChar::Press('`')), + 0x2A => Some(KeyChar::SpecialPress("shift")), + 0x2B => Some(KeyChar::Press('\\')), + 0x2C => Some(KeyChar::Press('z')), + 0x2D => Some(KeyChar::Press('x')), + 0x2E => Some(KeyChar::Press('c')), + 0x2F => Some(KeyChar::Press('v')), + 0x30 => Some(KeyChar::Press('b')), + 0x31 => Some(KeyChar::Press('n')), + 0x32 => Some(KeyChar::Press('m')), + 0x33 => Some(KeyChar::Press(',')), + 0x34 => Some(KeyChar::Press('.')), + 0x35 => Some(KeyChar::Press('/')), + // + 0x38 => Some(KeyChar::SpecialPress("alt")), + 0x39 => Some(KeyChar::Press(' ')), + // + 0xAA => Some(KeyChar::SpecialRelease("shift")), + // + 0xB8 => Some(KeyChar::SpecialRelease("alt")), + _ => None, + } +} + +//handle shift + key +pub fn uppercase_or_special(c: char) -> char { + let upper = c.to_uppercase().next().unwrap(); + if upper == c { + //special, the other keys on top + match c { + '1' => '!', + '2' => '@', + '3' => '#', + '4' => '$', + '5' => '%', + '6' => '^', + '7' => '&', + '8' => '*', + '9' => '(', + '0' => ')', + '-' => '_', + '=' => '+', + '[' => '{', + ']' => '}', + '\\' => '|', + ';' => ':', + '\'' => '"', + ',' => '<', + '.' => '>', + '/' => '?', + _ => c, + } + } else { + upper + } +} + diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..852e938 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,37 @@ +use linux_framebuffer::Framebuffer; + +mod framebuffer; +use framebuffer::FramebufferInfo; + +mod window_manager; +use window_manager::init; + +mod window_likes; + +mod components; + +mod themes; + +mod keyboard; + +mod messages; + +mod fs; + +fn main() { + let mut fb = Framebuffer::new("/dev/fb0").unwrap(); + let bytes_per_pixel = (fb.var_screen_info.bits_per_pixel as usize) / 8; + let fb_info = FramebufferInfo { + byte_len: (fb.var_screen_info.yres_virtual * fb.fix_screen_info.line_length) as usize, + width: fb.var_screen_info.xres_virtual as usize, + height: fb.var_screen_info.yres_virtual as usize, + bytes_per_pixel, + stride: fb.fix_screen_info.line_length as usize / bytes_per_pixel, + }; + println!("{:?}", fb_info); + + init(fb, fb_info); + + // +} + diff --git a/src/messages.rs b/src/messages.rs new file mode 100644 index 0000000..2ff7227 --- /dev/null +++ b/src/messages.rs @@ -0,0 +1,94 @@ +use std::boxed::Box; +use std::fmt; +use std::vec::Vec; + +use crate::keyboard::KeyChar; +use crate::framebuffer::Dimensions; +use crate::window_manager::WindowLike; + +pub enum WindowManagerMessage { + KeyChar(KeyChar), + // +} + +pub type WindowBox = Box; + +/* +impl PartialEq for WindowBox { + fn eq(&self, _other: &Self) -> bool { + //lol + true + } +} +*/ + +#[derive(PartialEq)] +pub enum WindowManagerRequest { + OpenWindow(&'static str), + CloseStartMenu, + Unlock, + Lock, + // +} + +impl fmt::Debug for WindowManagerRequest{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "WindowManagerRequest lmao") + } +} + +#[derive(PartialEq, Debug)] +pub enum WindowMessageResponse { + Request(WindowManagerRequest), + JustRerender, + DoNothing, +} + +pub struct KeyPress { + pub key: char, + pub held_special_keys: Vec<&'static str>, + // +} + +#[derive(Clone, Copy, PartialEq)] +pub enum Direction { + Left, + Down, + Up, + Right, +} + +//todo, rename to CommandType +#[derive(PartialEq)] +pub enum ShortcutType { + StartMenu, + SwitchWorkspace(u8), + MoveWindowToWorkspace(u8), + FocusNextWindow, + QuitWindow, + MoveWindow(Direction), + MoveWindowToEdge(Direction), + CenterWindow, + FullscreenWindow, + // +} + +pub type WindowsVec = Vec<(usize, &'static str)>; + +pub enum InfoType { + //let taskbar know what the current windows in the workspace are + WindowsInWorkspace(WindowsVec, usize), //Vec, focused id + // +} + +pub enum WindowMessage { + Init(Dimensions), + KeyPress(KeyPress), + Shortcut(ShortcutType), + Info(InfoType), + Focus, + Unfocus, + FocusClick, + ChangeDimensions(Dimensions), + // +} diff --git a/src/themes.rs b/src/themes.rs new file mode 100644 index 0000000..7ac1b99 --- /dev/null +++ b/src/themes.rs @@ -0,0 +1,44 @@ +use crate::framebuffer::RGBColor; + +#[derive(PartialEq, Default)] +pub enum Themes { + #[default] + Standard, + // +} + +pub struct ThemeInfo { + pub top: RGBColor, + pub background: RGBColor, + pub border_left_top: RGBColor, + pub border_right_bottom: RGBColor, + pub text: RGBColor, + pub text_top: RGBColor, + pub alt_background: RGBColor, + pub alt_text: RGBColor, + // +} + +const THEME_INFOS: [(Themes, ThemeInfo); 1] = [ + (Themes::Standard, ThemeInfo { + top: [0, 0, 128], + background: [192, 192, 192], + border_left_top: [255, 255, 255], + border_right_bottom: [0, 0, 0], + text: [0, 0, 0], + text_top: [255, 255, 255], + alt_background: [0, 0, 0], + alt_text: [255, 255, 255], + // + }), +]; + +pub fn get_theme_info(theme: &Themes) -> Option { + for pair in THEME_INFOS { + if &pair.0 == theme { + return Some(pair.1); + } + } + return None; +} + diff --git a/src/window_likes/desktop_background.rs b/src/window_likes/desktop_background.rs new file mode 100644 index 0000000..6c58da9 --- /dev/null +++ b/src/window_likes/desktop_background.rs @@ -0,0 +1,44 @@ +use std::vec; +use std::vec::Vec; + +use crate::window_manager::{ DrawInstructions, WindowLike, WindowLikeType, TASKBAR_HEIGHT, INDICATOR_HEIGHT }; +use crate::messages::{ WindowMessage, WindowMessageResponse }; +use crate::framebuffer::Dimensions; +use crate::themes::ThemeInfo; + +pub struct DesktopBackground { + dimensions: Dimensions, +} + +impl WindowLike for DesktopBackground { + fn handle_message(&mut self, message: WindowMessage) -> WindowMessageResponse { + match message { + WindowMessage::Init(dimensions) => { + self.dimensions = dimensions; + WindowMessageResponse::JustRerender + }, + _ => WindowMessageResponse::DoNothing, + } + } + + //simple + fn draw(&self, _theme_info: &ThemeInfo) -> Vec { + vec![DrawInstructions::Rect([0, 0], self.dimensions, [0, 128, 128])] + } + + //properties + fn subtype(&self) -> WindowLikeType { + WindowLikeType::DesktopBackground + } + + fn ideal_dimensions(&self, dimensions: Dimensions) -> Dimensions { + [dimensions[0], dimensions[1] - TASKBAR_HEIGHT - INDICATOR_HEIGHT] + } +} + +impl DesktopBackground { + pub fn new() -> Self { + Self { dimensions: [0, 0] } + } +} + diff --git a/src/window_likes/lock_screen.rs b/src/window_likes/lock_screen.rs new file mode 100644 index 0000000..76fc895 --- /dev/null +++ b/src/window_likes/lock_screen.rs @@ -0,0 +1,79 @@ +use std::vec; +use std::vec::Vec; + +use crate::framebuffer::Dimensions; +use crate::themes::ThemeInfo; +use crate::messages::{ WindowMessage, WindowMessageResponse, WindowManagerRequest }; +use crate::window_manager::{ DrawInstructions, WindowLike, WindowLikeType }; +use blake2::{ Blake2b512, Digest }; + +const PASSWORD_HASH: [u8; 64] = [220, 88, 183, 188, 240, 27, 107, 181, 58, 191, 198, 170, 114, 38, 7, 148, 6, 179, 75, 128, 231, 171, 172, 220, 85, 38, 36, 113, 116, 146, 70, 197, 163, 179, 158, 192, 130, 53, 247, 48, 47, 209, 95, 96, 179, 211, 4, 122, 254, 127, 21, 165, 139, 199, 151, 226, 216, 176, 123, 41, 194, 221, 58, 69]; + +pub struct LockScreen { + dimensions: Dimensions, + input_password: String, +} + +impl WindowLike for LockScreen { + fn handle_message(&mut self, message: WindowMessage) -> WindowMessageResponse { + match message { + WindowMessage::Init(dimensions) => { + self.dimensions = dimensions; + WindowMessageResponse::JustRerender + }, + WindowMessage::KeyPress(key_press) => { + if key_press.key == '𐘂' { //the enter key + //check password + let mut hasher = Blake2b512::new(); + hasher.update(self.input_password.as_bytes()); + if hasher.finalize() == PASSWORD_HASH.into() { + WindowMessageResponse::Request(WindowManagerRequest::Unlock) + } else { + self.input_password = String::new(); + WindowMessageResponse::JustRerender + } + } else if key_press.key == '𐘁' { //backspace + let p_len = self.input_password.len(); + if p_len != 0 { + self.input_password = self.input_password[..p_len - 1].to_string(); + } + WindowMessageResponse::JustRerender + } else { + self.input_password += &key_press.key.to_string(); + WindowMessageResponse::JustRerender + } + }, + _ => WindowMessageResponse::DoNothing, + } + } + + fn draw(&self, _theme_info: &ThemeInfo) -> Vec { + vec![ + DrawInstructions::Rect([0, 0], self.dimensions, [0, 0, 0]), + DrawInstructions::Text([4, 4], "times-new-roman", "The bulldozer outside the kitchen window was quite a big one.".to_string(), [255, 255, 255], [0, 0, 0], None), + DrawInstructions::Text([4, 4 + 16], "times-new-roman", "\"Yellow,\" he thought, and stomped off back to his bedroom to get dressed.".to_string(), [255, 255, 255], [0, 0, 0], None), + DrawInstructions::Text([4, 4 + 16 * 2], "times-new-roman", "He stared at it.".to_string(), [255, 255, 255], [0, 0, 0], None), + DrawInstructions::Text([4, 4 + 16 * 3], "times-new-roman", "Password: ".to_string(), [255, 255, 255], [0, 0, 0], None), + DrawInstructions::Text([77, 4 + 16 * 3], "times-new-roman", "*".repeat(self.input_password.len()), [255, 255, 255], [0, 0, 0], None), + ] + } + + //properties + fn subtype(&self) -> WindowLikeType { + WindowLikeType::LockScreen + } + + fn ideal_dimensions(&self, dimensions: Dimensions) -> Dimensions { + dimensions //fullscreen + } +} + +impl LockScreen { + pub fn new() -> Self { + Self { + dimensions: [0, 0], + input_password: String::new(), + } + } +} + diff --git a/src/window_likes/minesweeper.rs b/src/window_likes/minesweeper.rs new file mode 100644 index 0000000..664f4f2 --- /dev/null +++ b/src/window_likes/minesweeper.rs @@ -0,0 +1,350 @@ +use std::vec::Vec; +use std::vec; +use std::collections::VecDeque; +use core::convert::TryFrom; + +use crate::window_manager::{ DrawInstructions, WindowLike, WindowLikeType, WINDOW_TOP_HEIGHT }; +use crate::messages::{ WindowMessage, WindowMessageResponse }; +use crate::framebuffer::Dimensions; +use crate::themes::ThemeInfo; + +const HEX_CHARS: [char; 16] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']; + +fn u8_to_hex(u: u8) -> String { + let mut h = String::new(); + h.push(HEX_CHARS[(u / 16) as usize]); + h.push(HEX_CHARS[(u % 16) as usize]); + h +} + +fn hex_to_u8(c1: char, c2: char) -> u8 { + (HEX_CHARS.iter().position(|c| c == &c1).unwrap() * 16 + HEX_CHARS.iter().position(|c| c == &c2).unwrap()) as u8 +} + +//16x16 with 40 mines + +#[derive(Default)] +struct MineTile { + mine: bool, + revealed: bool, + touching: u8, +} + +#[derive(Default, PartialEq)] +enum MinesweeperState { + #[default] + Seed, + BeforePlaying, + Playing, + Won, + Lost, +} + +#[derive(Default)] +pub struct Minesweeper { + dimensions: Dimensions, + state: MinesweeperState, + tiles: [[MineTile; 16]; 16], + random_chars: String, + random_seed: u32, //user types in random keyboard stuff at beginning + first_char: char, //defaults to '\0' +} + +impl WindowLike for Minesweeper { + fn handle_message(&mut self, message: WindowMessage) -> WindowMessageResponse { + match message { + WindowMessage::Init(dimensions) => { + self.dimensions = dimensions; + WindowMessageResponse::JustRerender + }, + WindowMessage::KeyPress(key_press) => { + if self.state == MinesweeperState::Seed { + if self.random_chars.len() == 4 { + let mut r_chars = self.random_chars.chars(); + self.random_seed = ((r_chars.next().unwrap() as u8 as u32) << 24) | ((r_chars.next().unwrap() as u8 as u32) << 16) | ((r_chars.next().unwrap() as u8 as u32) << 8) | (r_chars.next().unwrap() as u8 as u32); + self.random_chars = String::new(); + self.state = MinesweeperState::BeforePlaying; + } else { + if u8::try_from(key_press.key).is_ok() { + self.random_chars.push(key_press.key); + } + } + WindowMessageResponse::JustRerender + } else if self.state == MinesweeperState::BeforePlaying || self.state == MinesweeperState::Playing { + if key_press.key == '𐘁' { //backspace + self.first_char = '\0'; + WindowMessageResponse::DoNothing + } else if self.first_char == '\0' { + if HEX_CHARS.iter().find(|c| c == &&key_press.key).is_some() { + self.first_char = key_press.key; + } + WindowMessageResponse::DoNothing + } else if HEX_CHARS.iter().find(|c| c == &&key_press.key).is_some() { + let u = hex_to_u8(self.first_char, key_press.key) as usize; + let y = u / 16; + let x = u % 16; + if HEX_CHARS.iter().find(|c| c == &&key_press.key).is_some() { + if self.state == MinesweeperState::BeforePlaying { + loop { + self.new_tiles(); + if self.tiles[y][x].touching == 0 && !self.tiles[y][x].mine { + break; + } + } + } + self.state = MinesweeperState::Playing; + //if that tile not reveal it, reveal it and all adjacent zero touching squares, etc + if self.tiles[y][x].mine { + self.tiles[y][x].revealed = true; + self.state = MinesweeperState::Lost; + } else if self.tiles[y][x].touching == 0 { + let mut queue = VecDeque::new(); + queue.push_back([x, y]); + let mut to_reveal = Vec::new(); + while queue.len() > 0 { + let current = queue.pop_front().unwrap(); + self.on_adjacent_tiles(current[0], current[1], |x2, y2| { + if !queue.contains(&[x2, y2]) && !to_reveal.contains(&[x2, y2]) { + if self.tiles[y2][x2].touching == 0 { + queue.push_back([x2, y2]); + } else { + to_reveal.push([x2, y2]); + } + } + }, false); + to_reveal.push(current); + } + for r in to_reveal { + self.tiles[r[1]][r[0]].revealed = true; + } + } else { + self.tiles[y][x].revealed = true; + } + self.first_char = '\0'; + if self.state != MinesweeperState::Lost { + //check for win + let mut won = true; + for y in 0..16 { + for x in 0..16 { + let tile = &self.tiles[y][x]; + if !tile.revealed && !tile.mine { + won = false; + } + } + } + if won { + self.state = MinesweeperState::Won; + } + } + WindowMessageResponse::JustRerender + } else { + WindowMessageResponse::DoNothing + } + } else { + WindowMessageResponse::DoNothing + } + } else { + self.tiles = Default::default(); + self.state = MinesweeperState::Seed; + WindowMessageResponse::DoNothing + } + }, + _ => WindowMessageResponse::DoNothing, + } + } + + fn draw(&self, theme_info: &ThemeInfo) -> Vec { + if self.state == MinesweeperState::Seed { + vec![ + DrawInstructions::Text([4, WINDOW_TOP_HEIGHT + 4], "times-new-roman", "Type in random characters to initalise the seed".to_string(), theme_info.text, theme_info.background, None), + DrawInstructions::Text([4, WINDOW_TOP_HEIGHT + 4 + 16], "times-new-roman", self.random_chars.clone(), theme_info.text, theme_info.background, None), + ] + } else { + let mut instructions = vec![ + //top border + DrawInstructions::Rect([1, WINDOW_TOP_HEIGHT], [self.dimensions[0] - 7, 5], [128, 128, 128]), + DrawInstructions::Rect([self.dimensions[0] - 6, WINDOW_TOP_HEIGHT], [4, 1], [128, 128, 128]), + DrawInstructions::Rect([self.dimensions[0] - 6, WINDOW_TOP_HEIGHT + 1], [3, 1], [128, 128, 128]), + DrawInstructions::Rect([self.dimensions[0] - 6, WINDOW_TOP_HEIGHT + 2], [2, 1], [128, 128, 128]), + DrawInstructions::Rect([self.dimensions[0] - 6, WINDOW_TOP_HEIGHT + 3], [1, 1], [128, 128, 128]), + DrawInstructions::Rect([self.dimensions[0] - 6, WINDOW_TOP_HEIGHT + 4], [1, 1], [128, 128, 128]), + //left border + DrawInstructions::Rect([1, WINDOW_TOP_HEIGHT], [5, self.dimensions[1] - WINDOW_TOP_HEIGHT - 5], [128, 128, 128]), + DrawInstructions::Rect([1, self.dimensions[1] - 5], [1, 4], [128, 128, 128]), + DrawInstructions::Rect([2, self.dimensions[1] - 5], [1, 3], [128, 128, 128]), + DrawInstructions::Rect([3, self.dimensions[1] - 5], [1, 2], [128, 128, 128]), + DrawInstructions::Rect([4, self.dimensions[1] - 5], [1, 1], [128, 128, 128]), + //bottom border + DrawInstructions::Rect([6, self.dimensions[1] - 6], [self.dimensions[0] - 2, 5], [255, 255, 255]), + DrawInstructions::Rect([5, self.dimensions[1] - 5], [1, 4], [255, 255, 255]), + DrawInstructions::Rect([4, self.dimensions[1] - 4], [1, 3], [255, 255, 255]), + DrawInstructions::Rect([3, self.dimensions[1] - 3], [1, 2], [255, 255, 255]), + DrawInstructions::Rect([2, self.dimensions[1] - 2], [1, 1], [255, 255, 255]), + //right border + DrawInstructions::Rect([self.dimensions[0] - 6, WINDOW_TOP_HEIGHT + 5], [5, self.dimensions[1] - WINDOW_TOP_HEIGHT], [255, 255, 255]), + DrawInstructions::Rect([self.dimensions[0] - 2, WINDOW_TOP_HEIGHT], [1, 5], [255, 255, 255]), + DrawInstructions::Rect([self.dimensions[0] - 3, WINDOW_TOP_HEIGHT + 1], [1, 4], [255, 255, 255]), + DrawInstructions::Rect([self.dimensions[0] - 4, WINDOW_TOP_HEIGHT + 2], [1, 3], [255, 255, 255]), + DrawInstructions::Rect([self.dimensions[0] - 5, WINDOW_TOP_HEIGHT + 3], [1, 2], [255, 255, 255]), + DrawInstructions::Rect([self.dimensions[0] - 6, WINDOW_TOP_HEIGHT + 4], [1, 1], [255, 255, 255]), + ]; + let tile_size = (self.dimensions[0] - 10) / 16; + for y in 0..16 { + for x in 0..16 { + let tile = &self.tiles[y][x]; + if tile.revealed { + if tile.mine { + instructions.push(DrawInstructions::Text([x * tile_size + tile_size / 2 + 2, WINDOW_TOP_HEIGHT + y * tile_size + tile_size / 2], "times-new-roman", "x".to_string(), [255, 0, 0], theme_info.background, None)); + } else { + let color = match tile.touching { + 1 => [0, 0, 255], + 2 => [0, 255, 0], + 3 => [255, 0, 0], + 4 => [128, 0, 128], + 5 => [176, 48, 96], + 6 => [127, 255, 212], + 7 => [0, 0, 0], + //8 + _ => [128, 128, 128], + }; + instructions.push(DrawInstructions::Text([x * tile_size + tile_size / 2 + 5, WINDOW_TOP_HEIGHT + y * tile_size + tile_size / 2 + 2], "times-new-roman", tile.touching.to_string(), color, theme_info.background, None)); + } + } else { + let top_left = [x * tile_size + 6, WINDOW_TOP_HEIGHT + y * tile_size + 5]; + //do not do the corners in respect of our poor poor heap (vector size too big would be bad) + instructions.extend(vec![ + //top border + DrawInstructions::Rect([top_left[0], top_left[1]], [tile_size - 3, 3], [255, 255, 255]), + // + //left border + DrawInstructions::Rect([top_left[0], top_left[1]], [3, tile_size - 3], [255, 255, 255]), + // + //bottom border + DrawInstructions::Rect([top_left[0] + 3, top_left[1] + tile_size - 4], [tile_size - 4, 3], [128, 128, 128]), + // + //right bottom + DrawInstructions::Rect([top_left[0] + tile_size - 4, top_left[1] + 3], [3, tile_size - 4], [128, 128, 128]), + // + DrawInstructions::Text([x * tile_size + tile_size / 2 - 2, WINDOW_TOP_HEIGHT + y * tile_size + tile_size / 2], "times-new-roman", u8_to_hex((y * 16 + x) as u8), theme_info.text, theme_info.background, None), + ]); + } + } + } + if self.state == MinesweeperState::Lost { + instructions.extend(vec![DrawInstructions::Text([4, WINDOW_TOP_HEIGHT + 4], "times-new-roman", "You LOST!!! Press a key to play again.".to_string(), theme_info.text, theme_info.background, None)]); + } else if self.state == MinesweeperState::Won { + instructions.extend(vec![DrawInstructions::Text([4, WINDOW_TOP_HEIGHT + 4], "times-new-roman", "You WON!!! Press a key to play again.".to_string(), theme_info.text, theme_info.background, None)]); + } + instructions + } + } + + //properties + fn title(&self) -> &'static str { + "Minesweeper" + } + + fn subtype(&self) -> WindowLikeType { + WindowLikeType::Window + } + + fn ideal_dimensions(&self, _dimensions: Dimensions) -> Dimensions { + [410, 410 + WINDOW_TOP_HEIGHT] + } +} + +impl Minesweeper { + pub fn new() -> Self { + Default::default() + } + + //https://en.wikipedia.org/wiki/Xorshift + //from 0 to 15 + pub fn random(&mut self) -> usize { + let mut x = self.random_seed; + x ^= x << 13; + x ^= x >> 17; + x ^= x << 5; + self.random_seed = x; + self.random_seed as usize % 16 + } + + pub fn on_adjacent_tiles(&self, x: usize, y: usize, mut action: impl FnMut(usize, usize) -> (), if_mine: bool) { + if y > 0 { + //above + if self.tiles[y - 1][x].mine == if_mine { + action(x, y - 1); + } + if x > 0 { + //above to the left + if self.tiles[y - 1][x - 1].mine == if_mine { + action(x - 1, y - 1); + } + } + if x < 15 { + //above to the right + if self.tiles[y - 1][x + 1].mine == if_mine { + action(x + 1, y - 1); + } + } + } + if x > 0 { + //to the left + if self.tiles[y][x - 1].mine == if_mine { + action(x - 1, y); + } + } + if x < 15 { + //to the right + if self.tiles[y][x + 1].mine == if_mine { + action(x + 1, y); + } + } + if y < 15 { + //below + if self.tiles[y + 1][x].mine == if_mine { + action(x, y + 1); + } + if x > 0 { + //below to the left + if self.tiles[y + 1][x - 1].mine == if_mine { + action(x - 1, y + 1); + } + } + if x < 15 { + //below to the right + if self.tiles[y + 1][x + 1].mine == if_mine { + action(x + 1, y + 1); + } + } + } + } + + pub fn new_tiles(&mut self) { + self.tiles = Default::default(); + //40 mines + for _ in 0..40 { + loop { + let x = self.random(); + let y = self.random(); + // + if !self.tiles[y][x].mine { + self.tiles[y][x].mine = true; + break; + } + } + } + //calculate touching + for y in 0..16 { + for x in 0..16 { + let mut touching = 0; + self.on_adjacent_tiles(x, y, |_, _| { + touching += 1; + }, true); + self.tiles[y][x].touching = touching; + } + } + } + // +} + diff --git a/src/window_likes/mod.rs b/src/window_likes/mod.rs new file mode 100644 index 0000000..31435b7 --- /dev/null +++ b/src/window_likes/mod.rs @@ -0,0 +1,9 @@ +pub mod desktop_background; +pub mod taskbar; +pub mod start_menu; +pub mod lock_screen; +pub mod workspace_indicator; + +pub mod minesweeper; +pub mod terminal; + diff --git a/src/window_likes/start_menu.rs b/src/window_likes/start_menu.rs new file mode 100644 index 0000000..17a82ed --- /dev/null +++ b/src/window_likes/start_menu.rs @@ -0,0 +1,191 @@ +use std::vec; +use std::vec::Vec; +use std::boxed::Box; + +use crate::window_manager::{ DrawInstructions, WindowLike, WindowLikeType }; +use crate::messages::{ WindowMessage, WindowMessageResponse, WindowManagerRequest }; +use crate::framebuffer::Dimensions; +use crate::themes::ThemeInfo; +use crate::components::Component; +use crate::components::highlight_button::HighlightButton; + +static CATEGORIES: [&'static str; 9] = ["About", "Utils", "Games", "Editing", "Files", "System", "Misc", "Help", "Logout"]; + +#[derive(Clone)] +enum StartMenuMessage { + CategoryClick(&'static str), + WindowClick(&'static str), + Back, + ChangeAcknowledge, +} + +pub struct StartMenu { + dimensions: Dimensions, + components: Vec + Send>>, + current_focus: String, + old_focus: String, + y_each: usize, +} + +impl WindowLike for StartMenu { + fn handle_message(&mut self, message: WindowMessage) -> WindowMessageResponse { + match message { + WindowMessage::Init(dimensions) => { + self.dimensions = dimensions; + self.y_each = (self.dimensions[1] - 1) / CATEGORIES.len(); + self.add_category_components(); + WindowMessageResponse::JustRerender + }, + WindowMessage::KeyPress(key_press) => { + //up and down + if key_press.key == 'k' || key_press.key == 'j' { + let old_focus_index = self.get_focus_index().unwrap(); + self.components[old_focus_index].handle_message(WindowMessage::Unfocus); + let current_focus_index = if key_press.key == 'j' { + if old_focus_index + 1 == self.components.len() { + 0 + } else { + old_focus_index + 1 + } + } else { + if old_focus_index == 0 { + self.components.len() - 1 + } else { + old_focus_index - 1 + } + }; + self.old_focus = self.current_focus.to_string(); + self.current_focus = self.components[current_focus_index].name().to_string(); + self.components[current_focus_index].handle_message(WindowMessage::Focus); + WindowMessageResponse::JustRerender + } else if key_press.key == '𐘂' { //the enter key + let focus_index = self.get_focus_index(); + if let Some(focus_index) = focus_index { + let r = self.components[focus_index].handle_message(WindowMessage::FocusClick); + self.handle_start_menu_message(r) + } else { + WindowMessageResponse::DoNothing + } + } else { + let current_focus_index = self.get_focus_index().unwrap(); + if let Some(n_index) = self.components[current_focus_index..].iter().position(|c| c.name().chars().next().unwrap_or('𐘂').to_lowercase().next().unwrap() == key_press.key) { + //now old focus, not current focus + self.components[current_focus_index].handle_message(WindowMessage::Unfocus); + self.old_focus = self.current_focus.clone(); + self.current_focus = self.components[current_focus_index + n_index].name().to_string(); + self.components[current_focus_index + n_index].handle_message(WindowMessage::Focus); + WindowMessageResponse::JustRerender + } else { + WindowMessageResponse::DoNothing + } + } + }, + _ => WindowMessageResponse::DoNothing, + } + } + + fn draw(&self, theme_info: &ThemeInfo) -> Vec { + let mut instructions = vec![ + //top thin border + DrawInstructions::Rect([0, 0], [self.dimensions[0], 1], theme_info.border_left_top), + //right thin border + DrawInstructions::Rect([self.dimensions[0] - 1, 0], [1, self.dimensions[1]], theme_info.border_right_bottom), + //background + DrawInstructions::Rect([0, 1], [self.dimensions[0] - 1, self.dimensions[1] - 1], theme_info.background), + //mingde logo + DrawInstructions::Mingde([2, 2]), + //I truly don't know why, it should be - 44 but - 30 seems to work better :shrug: + DrawInstructions::Gradient([2, 42], [40, self.dimensions[1] - 30], [255, 201, 14], [225, 219, 77], 15), + ]; + for component in &self.components { + instructions.extend(component.draw(theme_info)); + } + instructions + } + + //properties + fn subtype(&self) -> WindowLikeType { + WindowLikeType::StartMenu + } + + fn ideal_dimensions(&self, _dimensions: Dimensions) -> Dimensions { + [175, 250] + } +} + +impl StartMenu { + pub fn new() -> Self { + Self { + dimensions: [0, 0], + components: Vec::new(), + current_focus: String::new(), //placeholder, will be set in init + old_focus: String::new(), + y_each: 0, //will be set in add_category_components + } + } + + fn handle_start_menu_message(&mut self, message: Option) -> WindowMessageResponse { + if let Some(message) = message { + match message { + StartMenuMessage::CategoryClick(name) => { + if name == "Logout" { + WindowMessageResponse::Request(WindowManagerRequest::Lock) + } else { + self.current_focus = "Back".to_string(); + self.components = vec![ + Box::new(HighlightButton::new( + "Back".to_string(), [42, 1], [self.dimensions[0] - 42 - 1, self.y_each], "Back", StartMenuMessage::Back, StartMenuMessage::ChangeAcknowledge, true + )) + ]; + //add window buttons + let mut to_add: Vec<&str> = Vec::new(); + if name == "Games" { + to_add.push("Minesweeper"); + } else if name == "Files" { + to_add.push("Terminal"); + } + // + for a in 0..to_add.len() { + let w_name = to_add[a]; + self.components.push(Box::new(HighlightButton::new( + w_name.to_string(), [42, (a + 1) * self.y_each], [self.dimensions[0] - 42 - 1, self.y_each], w_name, StartMenuMessage::WindowClick(w_name), StartMenuMessage::ChangeAcknowledge, false + ))); + } + WindowMessageResponse::JustRerender + } + }, + StartMenuMessage::WindowClick(name) => { + //open the selected window + WindowMessageResponse::Request(WindowManagerRequest::OpenWindow(name)) + }, + StartMenuMessage::Back => { + self.add_category_components(); + WindowMessageResponse::JustRerender + }, + StartMenuMessage::ChangeAcknowledge => { + // + WindowMessageResponse::JustRerender + }, + } + } else { + //maybe should be JustRerender? + WindowMessageResponse::DoNothing + } + } + + pub fn add_category_components(&mut self) { + self.current_focus = "About".to_string(); + self.components = Vec::new(); + for c in 0..CATEGORIES.len() { + let name = CATEGORIES[c]; + self.components.push(Box::new(HighlightButton::new( + name.to_string(), [42, self.y_each * c + 1], [self.dimensions[0] - 42 - 1, self.y_each], name, StartMenuMessage::CategoryClick(name), StartMenuMessage::ChangeAcknowledge, c == 0 + ))); + } + } + + pub fn get_focus_index(&self) -> Option { + self.components.iter().filter(|c| c.focusable()).position(|c| c.name() == &self.current_focus) + } +} + diff --git a/src/window_likes/taskbar.rs b/src/window_likes/taskbar.rs new file mode 100644 index 0000000..84da083 --- /dev/null +++ b/src/window_likes/taskbar.rs @@ -0,0 +1,128 @@ +use std::vec; +use std::vec::Vec; +use std::boxed::Box; + +use crate::window_manager::{ DrawInstructions, WindowLike, WindowLikeType, TASKBAR_HEIGHT }; +use crate::messages::{ WindowMessage, WindowMessageResponse, WindowManagerRequest, ShortcutType, InfoType, WindowsVec }; +use crate::framebuffer::Dimensions; +use crate::themes::ThemeInfo; +use crate::components::Component; +use crate::components::toggle_button::{ ToggleButton, ToggleButtonAlignment }; +use crate::window_likes::start_menu::StartMenu; + +const PADDING: usize = 4; +const META_WIDTH: usize = 175; //of the window button + +#[derive(Clone)] +enum TaskbarMessage { + ShowStartMenu, + HideStartMenu, + Nothing, + // +} + +pub struct Taskbar { + dimensions: Dimensions, + components: Vec + Send>>, + windows_in_workspace: WindowsVec, + focused_id: usize, +} + +impl WindowLike for Taskbar { + fn handle_message(&mut self, message: WindowMessage) -> WindowMessageResponse { + match message { + WindowMessage::Init(dimensions) => { + self.dimensions = dimensions; + self.components = vec![ + Box::new(ToggleButton::new("start-button".to_string(), [PADDING, PADDING], [44, self.dimensions[1] - (PADDING * 2)], "Start", TaskbarMessage::ShowStartMenu, TaskbarMessage::HideStartMenu, false, Some(ToggleButtonAlignment::Left))), + ]; + WindowMessageResponse::JustRerender + }, + WindowMessage::Shortcut(shortcut) => { + match shortcut { + ShortcutType::StartMenu => { + let start_index = self.components.iter().position(|c| c.name() == "start-button").unwrap(); + let start_response = self.components[start_index].handle_message(WindowMessage::FocusClick); + self.handle_taskbar_message(start_response) + } + _ => WindowMessageResponse::DoNothing, + } + }, + WindowMessage::Info(info) => { + match info { + InfoType::WindowsInWorkspace(windows, focused_id) => { + self.windows_in_workspace = windows; + self.focused_id = focused_id; + WindowMessageResponse::JustRerender + } + _ => WindowMessageResponse::DoNothing, + } + }, + _ => WindowMessageResponse::DoNothing, + } + } + + //simple + fn draw(&self, theme_info: &ThemeInfo) -> Vec { + let mut instructions = vec![ + //top thin white border + DrawInstructions::Rect([0, 0], [self.dimensions[0], 1], theme_info.border_left_top), + //the actual taskbar background + DrawInstructions::Rect([0, 1], [self.dimensions[0], self.dimensions[1] - 1], theme_info.background), + ]; + for component in &self.components { + instructions.extend(component.draw(theme_info)); + } + for wi in 0..self.windows_in_workspace.len() { + //if too many windows to fit in taskbar... + if wi > (self.dimensions[0] - 200) / META_WIDTH { + // + break; + } + let info = &self.windows_in_workspace[wi]; + let mut b = ToggleButton::new(info.1.to_string() + "-window", [PADDING * 2 + 44 + (META_WIDTH + PADDING) * wi, PADDING], [META_WIDTH, self.dimensions[1] - (PADDING * 2)], info.1, TaskbarMessage::Nothing, TaskbarMessage::Nothing, false, Some(ToggleButtonAlignment::Left)); + b.inverted = info.0 == self.focused_id; + instructions.extend(b.draw(theme_info)); + } + instructions + } + + //properties + fn subtype(&self) -> WindowLikeType { + WindowLikeType::Taskbar + } + + fn ideal_dimensions(&self, dimensions: Dimensions) -> Dimensions { + [dimensions[0], TASKBAR_HEIGHT] + } +} + +impl Taskbar { + pub fn new() -> Self { + Self { + dimensions: [0, 0], + components: Vec::new(), + windows_in_workspace: Vec::new(), + focused_id: 0, + } + } + + fn handle_taskbar_message(&mut self, message: Option) -> WindowMessageResponse { + if let Some(message) = message { + match message { + TaskbarMessage::ShowStartMenu => { + WindowMessageResponse::Request(WindowManagerRequest::OpenWindow("StartMenu")) + }, + TaskbarMessage::HideStartMenu => { + WindowMessageResponse::Request(WindowManagerRequest::CloseStartMenu) + }, + _ => WindowMessageResponse::DoNothing, + } + } else { + //maybe should be JustRerender? + WindowMessageResponse::DoNothing + } + } +} + + diff --git a/src/window_likes/terminal.rs b/src/window_likes/terminal.rs new file mode 100644 index 0000000..ef3a689 --- /dev/null +++ b/src/window_likes/terminal.rs @@ -0,0 +1,116 @@ +use std::vec::Vec; +use std::vec; + +use crate::window_manager::{ DrawInstructions, WindowLike, WindowLikeType, WINDOW_TOP_HEIGHT }; +use crate::messages::{ WindowMessage, WindowMessageResponse }; +use crate::framebuffer::Dimensions; +use crate::themes::ThemeInfo; + +const MONO_WIDTH: u8 = 8; +const LINE_HEIGHT: usize = 15; +const PADDING: usize = 4; + +#[derive(Default)] +pub struct Terminal { + dimensions: Dimensions, + lines: Vec, + actual_lines: Vec, //wrapping + actual_line_num: usize, //what line # is at the top, for scrolling + current_input: String, +} + +//for some reason key presses, then moving the window leaves the old window still there, behind it. weird + +impl WindowLike for Terminal { + fn handle_message(&mut self, message: WindowMessage) -> WindowMessageResponse { + match message { + WindowMessage::Init(dimensions) => { + self.dimensions = dimensions; + self.lines = vec!["Mingde Terminal".to_string(), "".to_string()]; + self.calc_actual_lines(); + WindowMessageResponse::JustRerender + }, + WindowMessage::KeyPress(key_press) => { + self.current_input += &key_press.key.to_string(); + self.calc_actual_lines(); + WindowMessageResponse::JustRerender + }, + WindowMessage::ChangeDimensions(dimensions) => { + self.dimensions = dimensions; + WindowMessageResponse::JustRerender + }, + // + _ => WindowMessageResponse::DoNothing, + } + } + + fn draw(&self, theme_info: &ThemeInfo) -> Vec { + let mut instructions = vec![ + DrawInstructions::Rect([0, 0], self.dimensions, theme_info.alt_background), + // + ]; + //add the visible lines of text + let end_line = self.actual_line_num + (self.dimensions[1] - WINDOW_TOP_HEIGHT- PADDING * 2) / LINE_HEIGHT; + let mut text_y = WINDOW_TOP_HEIGHT + PADDING; + for line_num in self.actual_line_num..end_line { + if line_num == self.actual_lines.len() { + break; + } + let line = self.actual_lines[line_num].clone(); + instructions.push(DrawInstructions::Text([PADDING, text_y], "times-new-roman", line, theme_info.alt_text, theme_info.alt_background, Some(MONO_WIDTH))); + text_y += LINE_HEIGHT; + } + instructions + } + + fn title(&self) -> &'static str { + "Terminal" + } + + fn subtype(&self) -> WindowLikeType { + WindowLikeType::Window + } + + fn ideal_dimensions(&self, _dimensions: Dimensions) -> Dimensions { + [410, 410 + WINDOW_TOP_HEIGHT] + } + + fn resizable(&self) -> bool { + true + } +} + +impl Terminal { + pub fn new() -> Self { + Default::default() + } + + fn calc_actual_lines(&mut self) { + self.actual_lines = Vec::new(); + let max_chars_per_line = (self.dimensions[0] - PADDING * 2) / MONO_WIDTH as usize; + for line_num in 0..=self.lines.len() { + let mut working_line = if line_num == self.lines.len() { + "$ ".to_string() + &self.current_input + "█" + } else { + self.lines[line_num].clone() + }; + //cannot index or do .len() because those count bytes not characters + loop { + if working_line.chars().count() <= max_chars_per_line { + + self.actual_lines.push(working_line); + break; + } else { + let mut working_line_chars = working_line.chars(); + let mut push_string = String::new(); + for i in 0..max_chars_per_line { + push_string += &working_line_chars.next().unwrap().to_string(); + } + self.actual_lines.push(push_string); + working_line = working_line_chars.collect(); + } + } + } + } +} + diff --git a/src/window_likes/workspace_indicator.rs b/src/window_likes/workspace_indicator.rs new file mode 100644 index 0000000..53b7b4b --- /dev/null +++ b/src/window_likes/workspace_indicator.rs @@ -0,0 +1,74 @@ +use std::vec; +use std::vec::Vec; + +use crate::window_manager::{ DrawInstructions, WindowLike, WindowLikeType, INDICATOR_HEIGHT }; +use crate::messages::{ WindowMessage, WindowMessageResponse, ShortcutType }; +use crate::framebuffer::Dimensions; +use crate::themes::ThemeInfo; + +const WIDTH: usize = 15; + +pub struct WorkspaceIndicator { + dimensions: Dimensions, + current_workspace: u8, +} + +impl WindowLike for WorkspaceIndicator { + fn handle_message(&mut self, message: WindowMessage) -> WindowMessageResponse { + match message { + WindowMessage::Init(dimensions) => { + self.dimensions = dimensions; + WindowMessageResponse::JustRerender + }, + WindowMessage::Shortcut(shortcut) => { + match shortcut { + ShortcutType::SwitchWorkspace(workspace) => { + self.current_workspace = workspace; + WindowMessageResponse::JustRerender + } + _ => WindowMessageResponse::DoNothing, + } + }, + _ => WindowMessageResponse::DoNothing, + } + } + + //simple + fn draw(&self, theme_info: &ThemeInfo) -> Vec { + let mut instructions = vec![ + //background + DrawInstructions::Rect([0, 0], [self.dimensions[0], self.dimensions[1] - 1], theme_info.background), + //bottom border + DrawInstructions::Rect([0, self.dimensions[1] - 1], [self.dimensions[0], 1], theme_info.border_right_bottom), + ]; + for w in 0..9 { + if w == self.current_workspace as usize { + instructions.push(DrawInstructions::Rect([w * WIDTH, 0], [WIDTH, self.dimensions[1]], theme_info.top)); + instructions.push(DrawInstructions::Text([w * WIDTH + 5, 4], "times-new-roman", (w + 1).to_string(), theme_info.text_top, theme_info.top, None)); + } else { + instructions.push(DrawInstructions::Text([w * WIDTH + 5, 4], "times-new-roman", (w + 1).to_string(), theme_info.text, theme_info.background, None)); + } + } + instructions + } + + //properties + fn subtype(&self) -> WindowLikeType { + WindowLikeType::WorkspaceIndicator + } + + fn ideal_dimensions(&self, dimensions: Dimensions) -> Dimensions { + [dimensions[0], INDICATOR_HEIGHT] + } +} + +impl WorkspaceIndicator { + pub fn new() -> Self { + Self { + dimensions: [0, 0], + current_workspace: 0, + } + } +} + + diff --git a/src/window_manager.rs b/src/window_manager.rs new file mode 100644 index 0000000..2532348 --- /dev/null +++ b/src/window_manager.rs @@ -0,0 +1,613 @@ +use std::vec::Vec; +use std::vec; +use std::collections::HashMap; +use std::fmt; +use std::boxed::Box; + +use linux_framebuffer::Framebuffer; +use std::sync::{ LazyLock, Mutex }; + +use crate::framebuffer::{ FramebufferWriter, FramebufferInfo, Point, Dimensions, RGBColor }; +use crate::window_likes::desktop_background::DesktopBackground; +use crate::window_likes::taskbar::Taskbar; +use crate::window_likes::lock_screen::LockScreen; +use crate::window_likes::workspace_indicator::WorkspaceIndicator; +use crate::themes::{ ThemeInfo, Themes, get_theme_info }; +use crate::keyboard::{ KeyChar, uppercase_or_special }; +use crate::messages::*; + +use crate::window_likes::start_menu::StartMenu; +use crate::window_likes::minesweeper::Minesweeper; +use crate::window_likes::terminal::Terminal; + +pub const TASKBAR_HEIGHT: usize = 38; +pub const INDICATOR_HEIGHT: usize = 20; +pub const WINDOW_TOP_HEIGHT: usize = 26; + +static WRITER: LazyLock> = LazyLock::new(|| Mutex::new(Default::default())); + +//todo: close start menu if window focus next shortcut done + +pub fn init(framebuffer: Framebuffer, framebuffer_info: FramebufferInfo) { + let dimensions = [framebuffer_info.width, framebuffer_info.height]; + + let mut temp_vec = vec![0 as u8; framebuffer_info.height * framebuffer_info.stride * framebuffer_info.bytes_per_pixel]; + WRITER.lock().unwrap().init(framebuffer_info.clone(), temp_vec); + + let mut wm: WindowManager = WindowManager::new(framebuffer, dimensions); + + wm.render(None, false); + + // +} + +pub fn min(one: usize, two: usize) -> usize { + if one > two { two } else { one } +} + +/* +pub fn keyboard_emit(key_char: KeyChar) { + let mut kc = key_char; + if let KeyChar::Press(c) = kc { + if WM.lock().held_special_keys.contains(&"shift") { + kc = KeyChar::Press(uppercase_or_special(c)); + } + } + //unsafe { SERIAL1.lock().write_text(&format!("{:?}", &kc)); } + WM.lock().handle_message(WindowManagerMessage::KeyChar(kc)); +} +*/ + +#[derive(Debug)] +pub enum DrawInstructions { + Rect(Point, Dimensions, RGBColor), + Text(Point, &'static str, String, RGBColor, RGBColor, Option), //font and text + Gradient(Point, Dimensions, RGBColor, RGBColor, usize), + Mingde(Point), +} + +#[derive(Debug, PartialEq)] +pub enum WindowLikeType { + LockScreen, + Window, + DesktopBackground, + Taskbar, + StartMenu, + WorkspaceIndicator, +} + +pub trait WindowLike { + fn handle_message(&mut self, message: WindowMessage) -> WindowMessageResponse; + + //properties + fn title(&self) -> &'static str { + "" + } + fn resizable(&self) -> bool { + false + } + fn subtype(&self) -> WindowLikeType; + fn draw(&self, theme_info: &ThemeInfo) -> Vec; + + fn ideal_dimensions(&self, dimensions: Dimensions) -> Dimensions; //needs &self or its not object safe or some bullcrap +} + +#[derive(PartialEq)] +pub enum Workspace { + All, + Workspace(u8), //goes from 0-8 +} + +pub struct WindowLikeInfo { + id: usize, + window_like: Box, + top_left: Point, + dimensions: Dimensions, + workspace: Workspace, + fullscreen: bool, +} + +impl fmt::Debug for WindowLikeInfo { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("WindowLikeInfo").field("id", &self.id).field("top_left", &self.top_left).field("dimensions", &self.dimensions).field("window_like", &"todo: print this out too").finish() + } +} + +pub struct WindowManager { + id_count: usize, + window_infos: Vec, + dimensions: Dimensions, + theme: Themes, + focused_id: usize, + held_special_keys: Vec<&'static str>, + locked: bool, + current_workspace: u8, + framebuffer: Framebuffer, +} + +//1 is up, 2 is down + +impl WindowManager { + pub fn new(framebuffer: Framebuffer, dimensions: Dimensions) -> Self { + let mut wm = WindowManager { + id_count: 0, + window_infos: Vec::new(), + dimensions, + theme: Themes::Standard, + focused_id: 0, + held_special_keys: Vec::new(), + locked: false, + current_workspace: 0, + framebuffer, + }; + wm.lock(); + wm + } + + pub fn add_window_like(&mut self, mut window_like: Box, top_left: Point, dimensions: Option) { + let subtype = window_like.subtype(); + let dimensions = dimensions.unwrap_or(window_like.ideal_dimensions(self.dimensions)); + self.id_count = self.id_count + 1; + let id = self.id_count; + self.focused_id = id; + window_like.handle_message(WindowMessage::Init(dimensions)); + self.window_infos.push(WindowLikeInfo { + id, + window_like, + top_left, + dimensions, + workspace: if subtype == WindowLikeType::Window { + Workspace::Workspace(self.current_workspace) + } else { + Workspace::All + }, + fullscreen: false, + }); + } + + fn get_focused_index(&self) -> Option { + self.window_infos.iter().position(|w| w.id == self.focused_id) + } + + //should return an iterator but fuck it! + fn get_windows_in_workspace(&self, include_non_window: bool) -> Vec<&WindowLikeInfo> { + self.window_infos.iter().filter(|w| { + match w.workspace { + Workspace::Workspace(workspace) => workspace == self.current_workspace, + _ => include_non_window, //filter out taskbar, indicator, background, start menu, etc if true + } + }).collect() + } + + fn lock(&mut self) { + self.locked = true; + self.window_infos = Vec::new(); + self.add_window_like(Box::new(LockScreen::new()), [0, 0], None); + } + + fn unlock(&mut self) { + self.locked = false; + self.window_infos = Vec::new(); + self.add_window_like(Box::new(DesktopBackground::new()), [0, INDICATOR_HEIGHT], None); + self.add_window_like(Box::new(Taskbar::new()), [0, self.dimensions[1] - TASKBAR_HEIGHT], None); + self.add_window_like(Box::new(WorkspaceIndicator::new()), [0, 0], None); + } + + //if off_only is true, also handle request + fn toggle_start_menu(&mut self, off_only: bool) -> WindowMessageResponse { + let start_menu_exists = self.window_infos.iter().find(|w| w.window_like.subtype() == WindowLikeType::StartMenu).is_some(); + if (start_menu_exists && off_only) || !off_only { + let taskbar_index = self.window_infos.iter().position(|w| w.window_like.subtype() == WindowLikeType::Taskbar).unwrap(); + self.focused_id = self.window_infos[taskbar_index].id; + if off_only { + self.handle_request(WindowManagerRequest::CloseStartMenu); + } + self.window_infos[taskbar_index].window_like.handle_message(WindowMessage::Shortcut(ShortcutType::StartMenu)) + } else { + WindowMessageResponse::DoNothing + } + } + + fn taskbar_update_windows(&mut self) { + let taskbar_index = self.window_infos.iter().position(|w| w.window_like.subtype() == WindowLikeType::Taskbar).unwrap(); + let mut relevant: WindowsVec = self.get_windows_in_workspace(false).iter().map(|w| (w.id, w.window_like.title())).collect(); + relevant.sort_by(|a, b| a.0.cmp(&b.0)); //sort by ids so order is consistent + let message = WindowMessage::Info(InfoType::WindowsInWorkspace( + relevant, + self.focused_id + )); + self.window_infos[taskbar_index].window_like.handle_message(message); + } + + fn move_index_to_top(&mut self, index: usize) { + let removed = self.window_infos.remove(index); + self.window_infos.push(removed); + } + + pub fn handle_message(&mut self, message: WindowManagerMessage) { + let mut use_saved_buffer = false; + let mut redraw_ids = None; + let response: WindowMessageResponse = match message { + WindowManagerMessage::KeyChar(key_char) => { + //check if is special key (key releases are guaranteed to be special keys) + //eg: ctrl, alt, command/windows, shift, or caps lock + match key_char { + KeyChar::Press(c) => { + let mut press_response = WindowMessageResponse::DoNothing; + if self.held_special_keys.contains(&"alt") && !self.locked { + //keyboard shortcut + let shortcuts = HashMap::from([ + ('s', ShortcutType::StartMenu), + (']', ShortcutType::FocusNextWindow), + ('q', ShortcutType::QuitWindow), + ('c', ShortcutType::CenterWindow), + ('f', ShortcutType::FullscreenWindow), + //move window a small amount + ('h', ShortcutType::MoveWindow(Direction::Left)), + ('j', ShortcutType::MoveWindow(Direction::Down)), + ('k', ShortcutType::MoveWindow(Direction::Up)), + ('l', ShortcutType::MoveWindow(Direction::Right)), + //move window to edges + ('H', ShortcutType::MoveWindowToEdge(Direction::Left)), + ('J', ShortcutType::MoveWindowToEdge(Direction::Down)), + ('K', ShortcutType::MoveWindowToEdge(Direction::Up)), + ('L', ShortcutType::MoveWindowToEdge(Direction::Right)), + // + //no 10th workspace + ('1', ShortcutType::SwitchWorkspace(0)), + ('2', ShortcutType::SwitchWorkspace(1)), + ('3', ShortcutType::SwitchWorkspace(2)), + ('4', ShortcutType::SwitchWorkspace(3)), + ('5', ShortcutType::SwitchWorkspace(4)), + ('6', ShortcutType::SwitchWorkspace(5)), + ('7', ShortcutType::SwitchWorkspace(6)), + ('8', ShortcutType::SwitchWorkspace(7)), + ('9', ShortcutType::SwitchWorkspace(8)), + //shfit + num key + ('!', ShortcutType::MoveWindowToWorkspace(0)), + ('@', ShortcutType::MoveWindowToWorkspace(1)), + ('#', ShortcutType::MoveWindowToWorkspace(2)), + ('$', ShortcutType::MoveWindowToWorkspace(3)), + ('%', ShortcutType::MoveWindowToWorkspace(4)), + ('^', ShortcutType::MoveWindowToWorkspace(5)), + ('&', ShortcutType::MoveWindowToWorkspace(6)), + ('*', ShortcutType::MoveWindowToWorkspace(7)), + ('(', ShortcutType::MoveWindowToWorkspace(8)), + // + ]); + if let Some(shortcut) = shortcuts.get(&c) { + match shortcut { + &ShortcutType::StartMenu => { + //send to taskbar + press_response = self.toggle_start_menu(false); + if press_response != WindowMessageResponse::Request(WindowManagerRequest::CloseStartMenu) { + //only thing that needs to be rerendered is the start menu and taskbar + let start_menu_id = self.id_count + 1; + let taskbar_id = self.window_infos.iter().find(|w| w.window_like.subtype() == WindowLikeType::Taskbar).unwrap().id; + redraw_ids = Some(vec![start_menu_id, taskbar_id]); + } + }, + &ShortcutType::MoveWindow(direction) | &ShortcutType::MoveWindowToEdge(direction) => { + if let Some(focused_index) = self.get_focused_index() { + let focused_info = &self.window_infos[focused_index]; + if focused_info.window_like.subtype() == WindowLikeType::Window && !focused_info.fullscreen { + let delta = 15; + let window_x = self.window_infos[focused_index].top_left[0]; + let window_y = self.window_infos[focused_index].top_left[1]; + let mut changed = true; + if direction == Direction::Left { + if window_x == 0 { + changed = false; + } else if window_x < delta || shortcut == &ShortcutType::MoveWindowToEdge(direction) { + self.window_infos[focused_index].top_left[0] = 0; + } else { + self.window_infos[focused_index].top_left[0] -= delta; + } + } else if direction == Direction::Down { + let max_y = self.dimensions[1] - TASKBAR_HEIGHT - focused_info.dimensions[1]; + if window_y == max_y { + changed = false; + } else if window_y > (max_y - delta) || shortcut == &ShortcutType::MoveWindowToEdge(direction) { + self.window_infos[focused_index].top_left[1] = max_y; + } else { + self.window_infos[focused_index].top_left[1] += delta; + } + } else if direction == Direction::Up { + let min_y = INDICATOR_HEIGHT; + if window_y == min_y { + changed = false; + } else if window_y < (min_y + delta) || shortcut == &ShortcutType::MoveWindowToEdge(direction) { + self.window_infos[focused_index].top_left[1] = min_y; + } else { + self.window_infos[focused_index].top_left[1] -= delta; + } + } else if direction == Direction::Right { + let max_x = self.dimensions[0] - focused_info.dimensions[0]; + if window_x == max_x { + changed = false; + } else if window_x > (max_x - delta) || shortcut == &ShortcutType::MoveWindowToEdge(direction) { + self.window_infos[focused_index].top_left[0] = max_x; + } else { + self.window_infos[focused_index].top_left[0] += delta; + } + } + if changed { + press_response = WindowMessageResponse::JustRerender; + //avoid drawing everything under the moving window, much more efficient + use_saved_buffer = true; + redraw_ids = Some(vec![self.focused_id]); + } + } + } + }, + &ShortcutType::SwitchWorkspace(workspace) => { + if self.current_workspace != workspace { + //close start menu if open + self.toggle_start_menu(true); + self.current_workspace = workspace; + //send to workspace indicator + let indicator_index = self.window_infos.iter().position(|w| w.window_like.subtype() == WindowLikeType::WorkspaceIndicator).unwrap(); + self.focused_id = self.window_infos[indicator_index].id; + self.window_infos[indicator_index].window_like.handle_message(WindowMessage::Shortcut(ShortcutType::SwitchWorkspace(self.current_workspace))); + self.taskbar_update_windows(); + press_response = WindowMessageResponse::JustRerender; + } + }, + &ShortcutType::MoveWindowToWorkspace(workspace) => { + if self.current_workspace != workspace { + if let Some(focused_index) = self.get_focused_index() { + if self.window_infos[focused_index].window_like.subtype() == WindowLikeType::Window { + self.window_infos[focused_index].workspace = Workspace::Workspace(workspace); + self.taskbar_update_windows(); + press_response = WindowMessageResponse::JustRerender; + } + } + } + }, + &ShortcutType::FocusNextWindow => { + let current_index = self.get_focused_index().unwrap_or(0); + let mut new_focus_index = current_index; + loop { + new_focus_index += 1; + if new_focus_index == self.window_infos.len() { + new_focus_index = 0; + } + if self.window_infos[new_focus_index].window_like.subtype() == WindowLikeType::Window && self.window_infos[new_focus_index].workspace == Workspace::Workspace(self.current_workspace) { + //switch focus to this + self.focused_id = self.window_infos[new_focus_index].id; + //elevate it to the top + self.move_index_to_top(new_focus_index); + self.taskbar_update_windows(); + press_response = WindowMessageResponse::JustRerender; + break; + } else if new_focus_index == current_index { + break; //did a full loop, found no windows + } + } + }, + &ShortcutType::QuitWindow => { + if let Some(focused_index) = self.get_focused_index() { + if self.window_infos[focused_index].window_like.subtype() == WindowLikeType::Window { + self.window_infos.remove(focused_index); + self.taskbar_update_windows(); + press_response = WindowMessageResponse::JustRerender; + } + } + }, + &ShortcutType::CenterWindow => { + if let Some(focused_index) = self.get_focused_index() { + let window_dimensions = &self.window_infos[focused_index].dimensions; + self.window_infos[focused_index].top_left = [self.dimensions[0] / 2 - window_dimensions[0] / 2, self.dimensions[1] / 2 - window_dimensions[1] / 2]; + use_saved_buffer = true; + press_response = WindowMessageResponse::JustRerender; + } + }, + &ShortcutType::FullscreenWindow => { + if let Some(focused_index) = self.get_focused_index() { + let window_like = &self.window_infos[focused_index].window_like; + if window_like.subtype() == WindowLikeType::Window && window_like.resizable() { + //toggle fullscreen + self.window_infos[focused_index].fullscreen ^= true; + //todo: send message to window about resize + let new_dimensions; + if self.window_infos[focused_index].fullscreen { + new_dimensions = [self.dimensions[0], self.dimensions[1] - TASKBAR_HEIGHT - INDICATOR_HEIGHT]; + self.window_infos[focused_index].top_left = [0, INDICATOR_HEIGHT]; + redraw_ids = Some(vec![self.window_infos[focused_index].id]); + } else { + new_dimensions = self.window_infos[focused_index].dimensions; + } + self.window_infos[focused_index].window_like.handle_message(WindowMessage::ChangeDimensions(new_dimensions)); + press_response = WindowMessageResponse::JustRerender; + } + } + } + }; + } + } else { + //send to focused window + if let Some(focused_index) = self.get_focused_index() { + press_response = self.window_infos[focused_index].window_like.handle_message(WindowMessage::KeyPress(KeyPress { + key: c, + held_special_keys: self.held_special_keys.clone(), + })); + //at most, only the focused window needs to be rerendered + redraw_ids = Some(vec![self.window_infos[focused_index].id]); + //requests can result in window openings and closings, etc + if press_response != WindowMessageResponse::JustRerender { + redraw_ids = None; + } + } + } + press_response + }, + KeyChar::SpecialPress(special_key) => { + //add to pressed keys + self.held_special_keys.push(special_key); + WindowMessageResponse::DoNothing + }, + KeyChar::SpecialRelease(special_key) => { + //remove it from pressed keys + let index = self.held_special_keys.iter().position(|sk| sk == &special_key).unwrap(); + self.held_special_keys.remove(index); + WindowMessageResponse::DoNothing + }, + } + }, + // + }; + if response != WindowMessageResponse::DoNothing { + match response { + WindowMessageResponse::Request(request) => self.handle_request(request), + _ => {}, + }; + self.render(redraw_ids, use_saved_buffer); + } + } + + pub fn handle_request(&mut self, request: WindowManagerRequest) { + let focused_index = self.get_focused_index().unwrap(); + let subtype = self.window_infos[focused_index].window_like.subtype(); + match request { + WindowManagerRequest::OpenWindow(w) => { + if subtype != WindowLikeType::Taskbar && subtype != WindowLikeType::StartMenu { + return; + } + let w: WindowBox = match w { + "Minesweeper" => Box::new(Minesweeper::new()), + "Terminal" => Box::new(Terminal::new()), + "StartMenu" => Box::new(StartMenu::new()), + _ => panic!("no such window"), + }; + //close start menu if open + self.toggle_start_menu(true); + let ideal_dimensions = w.ideal_dimensions(self.dimensions); + let top_left = match w.subtype() { + WindowLikeType::StartMenu => [0, self.dimensions[1] - TASKBAR_HEIGHT - ideal_dimensions[1]], + WindowLikeType::Window => [42, 42], + _ => [0, 0], + }; + self.add_window_like(w, top_left, Some(ideal_dimensions)); + self.taskbar_update_windows(); + }, + WindowManagerRequest::CloseStartMenu => { + if subtype != WindowLikeType::Taskbar && subtype != WindowLikeType::StartMenu { + return; + } + let start_menu_index = self.window_infos.iter().position(|w| w.window_like.subtype() == WindowLikeType::StartMenu); + if let Some(start_menu_index) = start_menu_index { + self.window_infos.remove(start_menu_index); + } + }, + WindowManagerRequest::Unlock => { + if subtype != WindowLikeType::LockScreen { + return; + } + self.unlock(); + }, + WindowManagerRequest::Lock => { + if subtype != WindowLikeType::StartMenu { + return; + } + self.lock(); + }, + }; + } + + //another issue with a huge vector of draw instructions; it takes up heap memory + pub fn render(&mut self, maybe_redraw_ids: Option>, use_saved_buffer: bool) { + let theme_info = get_theme_info(&self.theme).unwrap(); + //use in conjunction with redraw ids, so a window moving can work without redrawing everything, + //can just redraw the saved state + window + if use_saved_buffer { + WRITER.lock().unwrap().write_saved_buffer_to_raw(); + } + //get windows to redraw + let redraw_ids = maybe_redraw_ids.unwrap_or(Vec::new()); + let redraw_windows = self.get_windows_in_workspace(true); + let maybe_length = redraw_windows.len(); + let redraw_windows = redraw_windows.iter().filter(|w| { + //basically, maybe_redraw_ids was None + if redraw_ids.len() > 0 { + redraw_ids.contains(&w.id) + } else { + true + } + }); + //these are needed to decide when to snapshot + let max_index = if redraw_ids.len() > 0 { redraw_ids.len() } else { maybe_length } - 1; + let mut w_index = 0; + for window_info in redraw_windows { + //unsafe { SERIAL1.lock().write_text(&format!("{:?}\n", &window_info.window_like.subtype())); } + let window_dimensions = if window_info.fullscreen { + [self.dimensions[0], self.dimensions[1] - TASKBAR_HEIGHT - INDICATOR_HEIGHT] + } else { + window_info.dimensions + }; + let mut instructions = Vec::new(); + if window_info.window_like.subtype() == WindowLikeType::Window { + //if this is the top most window to draw, snapshot + if w_index == max_index && !use_saved_buffer && redraw_ids.len() == 0 { + WRITER.lock().unwrap().save_buffer(); + } + //draw window background + instructions.push(DrawInstructions::Rect([0, 0], window_dimensions, theme_info.background)); + } + instructions.extend(window_info.window_like.draw(&theme_info)); + if window_info.window_like.subtype() == WindowLikeType::Window { + //draw window top decorations and what not + instructions.extend(vec![ + //left top border + DrawInstructions::Rect([0, 0], [window_dimensions[0], 1], theme_info.border_left_top), + DrawInstructions::Rect([0, 0], [1, window_dimensions[1]], theme_info.border_left_top), + //top + DrawInstructions::Rect([1, 1], [window_dimensions[0] - 2, WINDOW_TOP_HEIGHT - 3], theme_info.top), + DrawInstructions::Text([4, 4], "times-new-roman", window_info.window_like.title().to_string(), theme_info.text_top, theme_info.top, None), + //top bottom border + DrawInstructions::Rect([1, WINDOW_TOP_HEIGHT - 2], [window_dimensions[0] - 2, 2], theme_info.border_left_top), + //right bottom border + DrawInstructions::Rect([window_dimensions[0] - 1, 1], [1, window_dimensions[1] - 1], theme_info.border_right_bottom), + DrawInstructions::Rect([1, window_dimensions[1] - 1], [window_dimensions[0] - 1, 1], theme_info.border_right_bottom), + ]); + } + let mut framebuffer_info = WRITER.lock().unwrap().get_info(); + let bytes_per_pixel = framebuffer_info.bytes_per_pixel; + let window_width = window_dimensions[0]; + let window_height = window_dimensions[1]; + framebuffer_info.width = window_width; + framebuffer_info.height = window_height; + framebuffer_info.stride = window_width; + //make a writer just for the window + let mut window_writer: FramebufferWriter = Default::default(); + let mut temp_vec = vec![0 as u8; window_width * window_height * bytes_per_pixel]; + window_writer.init(framebuffer_info, temp_vec); + for instruction in instructions { + //unsafe { SERIAL1.lock().write_text(&format!("{:?}\n", instruction)); } + match instruction { + DrawInstructions::Rect(top_left, dimensions, color) => { + //try and prevent overflows out of the window + let true_dimensions = [ + min(dimensions[0], window_dimensions[0] - top_left[0]), + min(dimensions[1], window_dimensions[1] - top_left[1]), + ]; + window_writer.draw_rect(top_left, true_dimensions, color); + }, + DrawInstructions::Text(top_left, font_name, text, color, bg_color, mono_width) => { + window_writer.draw_text(top_left, font_name, &text, color, bg_color, 1, mono_width); + }, + DrawInstructions::Mingde(top_left) => { + window_writer._draw_mingde(top_left); + }, + DrawInstructions::Gradient(top_left, dimensions, start_color, end_color, steps) => { + window_writer.draw_gradient(top_left, dimensions, start_color, end_color, steps); + }, + } + } + WRITER.lock().unwrap().draw_buffer(window_info.top_left, window_dimensions[1], window_dimensions[0] * bytes_per_pixel, &window_writer.get_buffer()); + w_index += 1; + //core::mem::drop(temp_vec); + } + self.framebuffer.write_frame(WRITER.lock().unwrap().get_buffer()); + } +} +