From 827fdefd8343f0e92f203d7dfafa34cb5cf91cc7 Mon Sep 17 00:00:00 2001 From: Varphone Wong Date: Tue, 12 Mar 2024 18:06:08 +0800 Subject: [PATCH 001/134] eframe: Added `App::raw_input_hook` allows for the manipulation or filtering of raw input events (#4008) # What's New * eframe: Added `App::raw_input_hook` allows for the manipulation or filtering of raw input events A filter applied to raw input before [`Self::update`] This allows for the manipulation or filtering of input events before they are processed by egui. This can be used to exclude specific keyboard shortcuts, mouse events, etc. Additionally, it can be used to add custom keyboard or mouse events generated by a virtual keyboard. * examples: Added an example to demonstrates how to implement a custom virtual keyboard. [eframe-custom-keypad.webm](https://github.com/emilk/egui/assets/1274171/a9dc8e34-2c35-4172-b7ef-41010b794fb8) --- Cargo.lock | 9 + crates/eframe/src/epi.rs | 18 ++ crates/eframe/src/native/epi_integration.rs | 2 + examples/custom_keypad/Cargo.toml | 23 ++ examples/custom_keypad/README.md | 7 + examples/custom_keypad/screenshot.png | Bin 0 -> 39832 bytes examples/custom_keypad/src/keypad.rs | 255 ++++++++++++++++++++ examples/custom_keypad/src/main.rs | 68 ++++++ 8 files changed, 382 insertions(+) create mode 100644 examples/custom_keypad/Cargo.toml create mode 100644 examples/custom_keypad/README.md create mode 100644 examples/custom_keypad/screenshot.png create mode 100644 examples/custom_keypad/src/keypad.rs create mode 100644 examples/custom_keypad/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 92948c4589f..ba8d5a17a5d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1072,6 +1072,15 @@ dependencies = [ "env_logger", ] +[[package]] +name = "custom_keypad" +version = "0.1.0" +dependencies = [ + "eframe", + "egui_extras", + "env_logger", +] + [[package]] name = "custom_plot_manipulation" version = "0.1.0" diff --git a/crates/eframe/src/epi.rs b/crates/eframe/src/epi.rs index e26bece3a8d..16a8a689102 100644 --- a/crates/eframe/src/epi.rs +++ b/crates/eframe/src/epi.rs @@ -196,6 +196,24 @@ pub trait App { fn persist_egui_memory(&self) -> bool { true } + + /// A hook for manipulating or filtering raw input before it is processed by [`Self::update`]. + /// + /// This function provides a way to modify or filter input events before they are processed by egui. + /// + /// It can be used to prevent specific keyboard shortcuts or mouse events from being processed by egui. + /// + /// Additionally, it can be used to inject custom keyboard or mouse events into the input stream, which can be useful for implementing features like a virtual keyboard. + /// + /// # Arguments + /// + /// * `_ctx` - The context of the egui, which provides access to the current state of the egui. + /// * `_raw_input` - The raw input events that are about to be processed. This can be modified to change the input that egui processes. + /// + /// # Note + /// + /// This function does not return a value. Any changes to the input should be made directly to `_raw_input`. + fn raw_input_hook(&mut self, _ctx: &egui::Context, _raw_input: &mut egui::RawInput) {} } /// Selects the level of hardware graphics acceleration. diff --git a/crates/eframe/src/native/epi_integration.rs b/crates/eframe/src/native/epi_integration.rs index ee54b521225..f27d011202e 100644 --- a/crates/eframe/src/native/epi_integration.rs +++ b/crates/eframe/src/native/epi_integration.rs @@ -274,6 +274,8 @@ impl EpiIntegration { let close_requested = raw_input.viewport().close_requested(); + app.raw_input_hook(&self.egui_ctx, &mut raw_input); + let full_output = self.egui_ctx.run(raw_input, |egui_ctx| { if let Some(viewport_ui_cb) = viewport_ui_cb { // Child viewport diff --git a/examples/custom_keypad/Cargo.toml b/examples/custom_keypad/Cargo.toml new file mode 100644 index 00000000000..1557b35c86c --- /dev/null +++ b/examples/custom_keypad/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "custom_keypad" +version = "0.1.0" +authors = ["Varphone Wong "] +license = "MIT OR Apache-2.0" +edition = "2021" +rust-version = "1.72" +publish = false + + +[dependencies] +eframe = { workspace = true, features = [ + "default", + "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO +] } + +# For image support: +egui_extras = { workspace = true, features = ["default", "image"] } + +env_logger = { version = "0.10", default-features = false, features = [ + "auto-color", + "humantime", +] } diff --git a/examples/custom_keypad/README.md b/examples/custom_keypad/README.md new file mode 100644 index 00000000000..9e5cdf7e8e9 --- /dev/null +++ b/examples/custom_keypad/README.md @@ -0,0 +1,7 @@ +Example showing how to implements a custom keypad. + +```sh +cargo run -p custom_keypad +``` + +![](screenshot.png) diff --git a/examples/custom_keypad/screenshot.png b/examples/custom_keypad/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..632459e51aa92fc9e642e0e99e3c3bd4319b4a37 GIT binary patch literal 39832 zcmeFZc{r5s`#%0yN|RC%QkG$Slx;GWQS!nJ zCWNe0wlRe4S!U+@jQ9Kf{d|t^Z~5!@`|s0nbZDgKxS#vJ?&~_Q^E|I!+&0lWdieBV z0052}=-)I00CrmdV7q?kAo!g>Q&m&o7nVRXJsqI3N017BV0YJsY6HN>c+NeS1K?-K zeSO7ngdEtW+#{ZOIV1c~wpxxOrtbWY~Jx z73OL%XzUb4$-SXF@#>Qbif3!$`QIqfRo(38S5G{c{Q4p=Y6Y3=-dhDHI>~`+15R~> z4ZxUxIC$2T1^nU>oxogk9*Yhi_$B(_A@E%R;Pn6e<^OEL|3t$7ch{li9h``2a>#j> zxVB2Pf&tM^vUWA^)i|#A$I8B9cR38S+=sw`gT?j7In8uZ2YCRX70%#vzOTc_#}1nZ z0M0nt<>(2BZMt=H4GW+fL%R|^41NmS;IEPQ1fqNI!MO4+i*g43lXeMcdH4~@@!^Un z=M4&xA8>9QCbOlVb9H$iG0X}WbL~d86!26joQ&!QfOO+cwnqk%u5TYSTd)8hOb;2K z6XkrpQU+#(ir*rv$+{gnViBRNz_%BXx17xm*`7+5hXR1xJ@komb{)PQl)NDf1pq-r zx|qUJtn7MQniv4ks^lTBE{JkoTPr*SwB-FZXPxq3rXQd2xe=Xh#r47?fI|AMI6h0t z?2t~VFaW*XsWUWt`A5e+Y%Z9dx^#x|RsI~WctklJGvej(PyQ4h0~DlZ1Ia|Xu!3By zY?qfDetmY0ChR5`$QuLE zD=uU%XI~w@!As@xMzAjcFxB2H)^ZyB#-tH9c!0yt*qPfp7oo}uywci@0Cf25j!eb% zrd3e;dddf&~Djd`zrq^u!;pc@jz*fRss67kA3cHnyWTK)3( z>3iKXYX|3^B|J&IB%%(U*%YCf_xf6_tkT_dSWojF3-HwY$FBY6OtgWhYld_FA2@|m z0B~OVp~ZgcRl<`;N2lWC*47VzJF@k(R>Y0W1s*C2GVnbuTizLb{hEZWwcqlroT&*< z4z%%00xe%$$Vc*2z;hp5Ppg!F07O4&*A%(F6)P)ysM&`F;FH_+0>)2N8RtFOl%W1= z?~g&4dmp({co4Xb|82xNW!UkharFQvcnH56Sh|eCPb(5f0fn&Ntt>%x9>Z1ex+I>0 z7xHaI%{Ez?q=RRCZp*@&8-MszcfXV{LZwInz{|-teweJOsphup%2yuKlO!!h1ftuS z@1mVW&KYB#HP88aKuNnp(S^^jqwlN6l@X8pyA6wV7wX0Wh+c&65!MxUmX54Ix=$Rx zywio)hwHt1al6ZSyiI)oT)7a*DRK5h6~Or-0siHcQAbtxmC=*xzdzgvrPot(FXTq; zP$!4o(HEVrtX-$T-4!6lGV!mBl8F`J5Xa zN%tj0jbyA=y}K@xG|U~@fEHu!nu5budF3Fs+;!_l9jqVZgvfo#+gvu>mD4GkU!@Ve zAHbU>ziTr5jL>Y+1RKCSS)CGU(E%gQwV+GcQnti7^zW}{M3XT9n1gPJ&(-}tnR*Yfl=vD zU$w^xwEWDC+#Fu@Z^8^*fFgiFNmmzfS=*|DJ#HkmFs)P?0FKD)S`D`on)!ejFz0Zu zwxNapumB!bn;&Ckf$rl~0KntEP=S?uR(SyMNCJ^a`uTPdWW-9RX;jq@cXTB48O$Gh zmDm=?&p+ymbqrM$l&ZnfcNVNE0FYVB3>h3d`Z#o*t~sZ%(}axj?fN`*ZEMUN%Hrk! zu=Naq)Ny6h$J_@;Eouu}fX-018?Q0;*4lVXHSUaskR#h_hn(zlVduHoeQSDk+ZM58 zCuoMCf5g<8xELx4PxD}d9&we=hVn(qWx78{_|B!h& z*}Lr}@66_0rgvpY(YjmjyITYG^dn&mkGuN`j#c4ZpMgu}wMGVQSw0wijk{~zD)Plz zTVsK3%z!v3TjDkhem{uTQLZ_hL>obbmRcgL6IGy#vZi&bH?9cZ&n2_ z9kre>B0vxZoLP9lGsv@eTyXf@%s9Ve(4Cdn{t@|~*87a&iqTOxTw4l<@WZ^T;QPd3 zB4#ekz;yLwtj$VlIwiACZjq75;c|%q0|1b9V6HT9?(H+aQGa~oFop1h-^s=yVx?_B zIY;7E=(`B| z@r=Lm1FEN*3ysqF@VgCodn=W(P)bhQ7M?+y&zxwYZ(!sp$@6JJuhf&*x06f`tO+o3rrxL%W{oS}T-*F{cWXREo-J0%-AihVs4-$7g%33e zE!V(k6pzs>8#aT&ML$8;;HSDqmO-HP7i-Qsj?kl<^Zc(w=?g})cbvG<7>M> z&VUEzkm;M)HmJ&YjmgYy?8GVxdvZB!sDTg~Nnb-_icAc6-dcS4&O&6q(;H%A&X^YF zBIXi?ny4o%z0|zBG`Z3)g7N$Kk2h?0WEBBdb>HqTaynL(-M?j8W??l$Gc~VkWKg43 zj<0w+2L#6bXSOjgh9OCRKuc^}aj!&cIH^u7D1TZaenF z6(KUKl3s_RcC4)T^x2WQlsR?l<_1wnrSoHhyes ziwRPv{tjTGEZ_XIUWaX}_~BWcLtvv*0~!Pq9PWQUFp#o@IBZ*Wq@Tm+3x*v=jVT;G zj2hee(|Sz4%(;jd2u)8df>%^&^p*JKwx{9uW~y4JO@e>8Jey<_`FmXzv%YQ4;hdqT zt69p1307pl7&}Aofq~XzS`JXhp-w5Z!&9+E@$dE1Nk(c<18QZTMd&J`gGo}6JTuOU zEXfH6ioixGv-)9Yk_6H==h=Y^_=MSLuN@X^d>=-eD|6Mz4G+c&Zn9mA{r~}){l}iX zGq`F^w~$&7yibsLFTU4M05ierPEjyFiul|bVA;_TERZ89%{9I#J9gdX} zeFM0mPND^LdZl<2G2|GR6Eab+k={X5jL)8@BpiNap*am)6TK~DGkt~1FHzh`x3DRg zKZhj-W>j!{;2NK5#E+>&Qg1%aS99)MHTpB)D*gw?J^?&A^;rtM9(Ljeut{y9&% zO%sy06nUjoXK3AlU8^R~&0*-FA(J|)?NjiIp*7OF#E|BNDI3bf^CT&p?UoC->zPE7 zS6v(pECS`CV0}>%s=CSba@8mR4X9jIOs*v8P%<6o&ey z&-JnAoAcxZbM!YE9x&C(5VVoH>ERn@L7YM^hm$XqX>nVLpPIMiY9VQ?by*+#8R$Gw z8EVAUW1hoP!ex-Ek2kxN94#TLEi8`_eR~tzXCd|Z#?o3fnpS?qvq&ARzQp->!B}Je zB1>z`_hqEGH@EGy9ExxKJk|^i{u! zdc{5Gl5aGGC^St`LKsSzXhDxJxW$K#Uskp^ zyebHJGC!-B*mCvPt(_8sesr+ecH({U1xGxu1;l-ROR_u5%%Cq$7L9vZ$`xTZLwjR! zXJaIS6UYO%SJF;vK(^E-UA{u>?o8f8QT@O-De06vmB`0O#C!}=bt7+&m#Dq9ER{S* zydXBPp6@r2GM|+3J)d6FtkTp`Wu$PjM11>4yQbBZII5MJkjaK_mz7JBAzH)faq(cg zexZqDMZdJG!HoWwf>_fGJ$&D9nVm@gKG}=9ila{)x4ETtU7~0S|)lW=f6=aos#bMoG0W{MQ@FNJx}YcF@L65 zA}MflA`5r8%zUk2{Y*0AJgqC@Z>xx$CVeG74cT8%8l^QquYn66NH< zqQzx6++p7^|7Biyj;C;5&NZp%d|+O z%hdWApFpkKajO6HE}0QEJY?du+(4!+1*rBE)%Ho|9Ct6s%P>LcUy7_R5F3uwC^0`Sv9OyR@`Z2Bi9ohr{ zvoQ$=oO*qg<7D_|M~x%R*2`X;5a>HH4c2A5ZIy0VA6b_xiOisHrCCB^JbZKe76XOW zl@X)jZV5$~`wQh0%}uY>{(gSNrQ_E#5lwGO6Jic^dya4c^Lxi4B-oz#ryHRq*C>-0kw;D}BK zD`u`J^A;~Br}j0Uhnb((M;^=tA(GRh)SmfDbze8@T?NZ%ZMmO;mI#E?EcHH!zO1nG zj*-qel;ILpVvvB9DxV?8=W)XEE(mX*w$GQx*rr9_%Wu9WOS&eGx}{GAlCELP%vp1& z^KTwhzp}reJO2i;D>Q<@;T{)q9l1xkwk0bf#bT`MOE_fYsgv6Cq<`6TXR`k1W6nXG zX&KHxTlSe`X)yroNVq;C_%H*~=1{=doDC(>FHyUT)?mtxbXI#scU1hksY+l{p_m`14%lAN0zok$Pl6~FSt5A9g=L`DoA{w? zqbkEwyuhyjnZte$Qr%bc-im~MA%hCK>wxh`TtA6WNb*Fqw>9Y(<2TflM{j=z85S@V z5{&oPb6!!$n5$eY^8R#QEzI=VhQ+o@e*br|YNd+a@vup0!fNhJ%F8@< z_ZUM<^HQkUtpT;JfN|@tb;+CKhPCG7{KPEs-t4M8&=PUH>Ux~X{&Mu?aC7eh*0b69 zmqSV6JHnk(kD#Sz%{F?WQf0LFS?3A`^cPry9w&Er>$}%i8yqloP00!tsva~u@e$0B zsg&hUQ*U&4jWfK5`Er6sJmJbFs41|G*{p-QtZM%AlJ^2mIQ4z<`cP8Kxn-kL_r75t zoy1o^`uzbjBb($ul4dy;9KLtZra#i!7xaW!3#G@6q2&PZ)S!d);)rGS`@1f04Ung) z&R19rGId=aj#|`qiCZ35)%Q<@(HELkxg82@4Ka1(sNI0+v|zJBGtyD?R6ow?QJAgYE+ z&RMC(&fTb$Hh$|NV}rIkgA@nk*n{>BWtr~-)-ama&y)?hHENOBVIa>S4t3G~3QlXS zH(4^OS13rBHlf9lu#LEkkK(c~Wu;Fc_u?^|yelfmHIV(_D~ON_xb7&DFbI_lIG!c* z+(K(QPnn~M+A1N|8?jAzR9Y%q@-XN zgk{9H&v<;MF9m!+w~u<2RS;sv1U1lsJ*xZt=(soGQoG>Vyf!j7L|A?FxrSA#aKKMm zGJ3C|QL|6LTQ|?3P{%Lrr)YfEcJZ)J!*^!dFUU=i4*F*>j)2Qj5_}(4295upd!8Zh+{ZXfd|EyxdD}cdinG6-M*f zSky)rn;LFBiwz~BUGJTPZB9I+V2f!&a+GuEsko<@krCaLEOa**hT)*}Jo$@Fsnm6w%^Q%TsRGdcv)( z_onH#m@1R$%Xcs&ft(BI_;rDl6R({6;OeNwFUms>=1^*Po*3@vzguaWJpxxd%!=aj z*^uXvpvFMtu_NYOa@Cj7^rL2NNl_=9MF}4A)K@|Iudm2tjfwxz&r4$KWfz1W>su9c{TI+b^C1f`pUDJ#dmghZRA==%ezU(0UVK%tlR9L+j1bF{Y z^*8#AJchmbC z3v(3uboqT_`;zf?L8Kxi*Z}gD*Y`T=?^=?dxs6nhv4$F}IL%^lX zB$`hU!-QTnqCu)8xP2yNOKo&J%(06+rd8Z^VF6xg`Y;)&s5MQq)5(s#ZM!wUp#lct z0vvTqj)IUl*;SPu_eYoy7vaY9a#Q`~k31GV9u@Fb`F1+H$9BDkZbqad*(8}T5jEa(&GIEdIV{1UAp zXD&44=BS@)j`EzqvzsD7-k5tfJ7TqSCj34@x;W@a#oaK(?Ll;`g)O>DSmLC?sq}@0 z#I4H;BMG?dmJ17aCl>tRO@?`?f27ebEQY7)kO*9)rUJyG3ix-o+qC_YL(3*& ztASi0$71|Oi4CM=#tDZ}B6~kM2EK3Nd>Ia$KY~z&B@_F?q-5%G*m9|&`=cA&WfbsiRb8Ew- zsJg#n6isXTe&wM1_3;mEQ_!$+kOB6C*aFTvg*V26*t42XDZbjoAZZJeHT@>`?$75k zTnpa4`rdE!tOn$F4coCiH`}oQU|c+9A3gj2|5pX?Kaz!eSJU3=Pyda)wYe=ihv5C| zA1A^VcrRF2mKOoQx7gQLM&8@hdRac0Nwu~z536R<)(*&nO-Q9n$D$?osmN!tAlF(c z#{zsSNOKAUtH?J%l*leHlK@9YZc5)k?JhN7GFgG^JWgQB0;6rnArPuu2lG{!dty+lacdcszgO&lE5JA7n~drz+RZ{VXxUB-Oyy8S`+pQ>sk5Md#bi zXCr`d2Bybeg&eUK5Kaf1W^mQ{;C3p%l2g)tCHJ*pcrCg$PK+EF@R9Uc&(eJE_l&hB z+MAKHgWu^gQHRm`1L|;G(VDWQ3`}|bhxd!y);-W&_3_u%jyBnAQ~tcxu=O=BLw8+% zR*yFc@W+R2)5RHdqhfybrA^f8UJlm5nhtlwL`lN7)e%dnNFt+{ZUQYK8PhH@9M?!? zMUoZQjz$Z}%G^u#bjncNV5G=sJlsIdAU2McmTdUiJ${-KJX*FFPR96Oq)+xQ%UM-~ zl{AjMNjQ+F3i9_LL2M=%H2t8Bu|TG6P5u^%tiHMyj)XY`QtJnl0R@3z^48DEaVeU~ zgWu1hFn{}yJ4k+a#-B3-$_w1g8-fM}smQ8taq)~eM#T@mww^X5eF%NQuW@J6OJ?ou zi~Ib}4qfo$e>&K$ zDZ}I=QFxcv-oN2p=HV6vh;8)8^)H+xYl=tlSR$+mH}V$d2wf~kP_(3gr^TJVOPr)i zS?m_j1>U+A_R^3s>ojen-JIp3;6E&chXBa-!jg;Ks!WwpUddUr?*KDpOR+ zXb?__$!IH}xUQ{#n*?#%g*xy$c^QU-w1a(Ug9?7Td*I~xm+=wPVr>-7t@=mLV#Yw~ z>!ncekGHs*`zh)_2HpfacCxn2mEA}gTny_mftE=&ehC(Feh4awh5Swr-ySiU*crrH znU4@RRF6l2B0-t7F^>0w)yd_^#vQ?+Z`uPXPu@;^ zlxs~b?YG|@0j4Mei;=dUtNb`9=AVsdIX~m}Tibv7X3}6z2c^`P8v{R%2aj5s4>6%$ zYN{Nw%B{@h$6J|a1VO9@?fsi3Ur6Pik4cKU=+yY|r(wEe?UJ!xWGG@-J}$>Q`hkTX zxw=U{>uH@elH;!&;PEu!$wvf8Ev9nda%3Qzpw2Q05^=I}jeov)9ZvRRQs!J5e_AGI zv;5^^a3E9Pe0B1Nspr1%_(19=4~JPy zPd<59>bQY(0NAitnB&$S9gD~V1%ip&VA9t*zl2+;^zynR81d&b9Dk9L8}9e7gf?V@ z3-Sz&lyST=@o;J~!#!78IBf2%VnQg0EX(Yn2DM!i;meK=pzPBI)(|VGBUk|*>V0(W zOqv9>p7NrT@WQXT;Y+r{U)|{faJP;5fi^Y`knh-}9+Zf7Iu+uq}vYY{~I39}w#GW3cxI?$C4 zvi5opx|N4cHvFzKS}9U#DzRyf2>l4kB>dppI;XK_=RM=| zL0c8WJjNV&^9;%!m4kN^pgcJEF{l;ZX5G_R`|0-{nwK#fDDu82m~u~K*Jg#jg~$K> z!ry$md6w}7(;}-mbQ@P2J(>nM62hOMzE`L1L^iQZQ09+!)y9wfQ4= zN9Ubxt$-98A7hI_L#9!xx%RSOa<K6pk6N_ty zjjyE2zN(Fr!|j)}&^(w?t3|7qG-we><(Oxp^iq4{&WNh)s{GVHk}lU-3&GH;`;DHS5@9?X zPLUjP&yaLB_>{0K7`fg%;4rBFArLHY{0+;^;i`D{qqe%9j|2Bs+O4sfNyjg;^fB2q zXoiUG8!)%{6aHL$GyzH#Hct&hk;O)6Ei~=7JJDI@QU6rDmQ!;GM8WPxYV2u<>AX2= zEhs~8VyF>q%U}27Zrmi8JzxS$u2B`-d+_nD-H{%U;kMJn4&Ij((m?L?qvgnP<^G|S zWw6j=L>W{S425V_1^)SZVe~J~ly5XV`WGkx4ssV1d=b~y9*6inei_SEvgaWsdY$VP z2@JzYCYQ1@z8r3@m*`wV%$8I;=i$-5IKSgkltzWLMN zEe4&;kE9JD9W~bf+-RHk!sYlyuUPs~?4K?y*K}_=0^g2^a^92Y_#A(2)5i8iQp})I zs6oQFO6ncI4+3}6Uu^0i7YIs>6kjAg97ATOH#w#oBp?fp6i7Q^KZ zv3S|Cn4YUsue=QJTsdacUs}#z0tld}>UGk?CZ|v{Ic|Ct0i!1BUp5enm1xbTMhB~p zAY?t|j`3L}-fkk;7}o6qtzxb&zY9H$qCT5iwlSHz)fQz-*2Myxn7S|s7Cn{WzEn1` zAxbc(LFQ(G>g`Ncgjl|`#N6EGCgk)x$K?0BU|&Sek+iH;iZQ%>AEdc0%?I^8l1o#m z-OjV-tZyv_#f`)MeC8-CG4Ine&RnkX=2kWi)mn47G9J|LMK}$=uIS5A-#oU2OV?~_ z&W5Rsjz`_b^pHEjU!FPr4w+$H0~G>BB^&i!8-c8++(A}VC$qg^YIGf*#Ky3QtV!-M z3UthDuSvdtj^Ggk6H$L;6#TmkyB~3XsXgNnGVFdyT2x+yk zD69GMSePnh)R7!DGJh8_LN#ruy`1S0 zf>shBys=$CsG=#!3nMvw97Y(iTSv_^*=8b5+p$PG1=&1Eoe#pWE``s1G~s`7xFlpAEn-B>*^;XCC^S$ph8QY{ZvAr5~+Ax}CyPY}m`k(Ix*IosB!<1?1&G#Iw zfkJe8u-f8tQ?da@-V{CIRkQ%4+ei8*!|IJf$$LY;=ye-F_X&{eW7fTLJN*M4>x7ip z^8%LVBkQJvYmc~Ov;6_lTFoL4Tyv(rNAF2D)1q(%uL@F*4Nj6JlAc414h4xrhRLPu z`~r}YWZCxhh2+Fy++x=%0;4;{Ido1$( z!)|XU^EX$L=M$_{#)HgRqj);>9>VLj-241@7q5Z9&+v!bDa-K-QSpY|DL6 z!e*|ne>7_PLA9}>7YWouS0=yRIChcgf>=$N`B$CF4gfD`8c~JlFITv2s%uOL*UVxT zVV|AAuP5{@%-v9kX>cXy%g|LvxW{Hz%mk7V`S$%GVS%b7iRA_Sjx}Q+(-K^AtB>?Q z@*ivsRwpdZ_J?^fasANZmED2S+Oh9-!c35?(0zd4;dVCIQo=3UKsMfK-<826C9BSf zMP}BVc_g{j`KL#q5$uFyjSgzbGDnfX_p_EOv&;05Xwf$>uTMFCQH&!8bju8+^sL&M)z*ML zZ^*dP?-X+r>8xaUN+JGl9R580&oRb_BR9E&5%+euf+987py(?QyQ(O9uL$i9DF0!Z zm~HsupPLO}k#P2Y#R3--m^T!2Rp(`PtbYVusN`8r3A9)8R8a*kj$ zY2&$)>Zu8^-o0e{G!`Fut3#B`AjyDOB(^}!t$D^-quZ)aOFwg^0H3hlpA_vP=X#2| zek{4&==KVDT9!lQiFEsb@w`XhnJpbmb!~96Y7J?%mqm)q%T&Xgsiqu$*SK`X@6%hK zq`~-LG}?kgqQ)?}woA+M=6FO8eU+0e?SyZYoAJf`ogu|Bb{Mqk`O|=|Wkb&(l0l+J zKKOe{gmPL z1X9#yb%3LnqVo$2nZi|>>yhjD!3 zva$*R$)M<7OaCcvRcohQxVaWB3Ru?zomGV<*)05XG|0DmDLC?fhma*G^cbjc{PSF^k)~qywkaDfGYgZEi%Y?3*Y?ajA4Z z{5va3qi3B5$mt#k4qT3zaPnEh3B4rt&^pyvjLJ;nwaO_66nZSGj1wfA&+o^eb*GMe zT_}yK4aVq~C$6PsuO&}*Ek`!(gMK1sTN(O^H62Bn$8Tfzkc;?%dTV+LqcWV~02{B| z!O!e{FSPb=m_;kEg|>~LNnufU$aQ;bFHxhG?y$%sKv>;eDx>uB#Ui2DrQjk8ZH zcm=(CHYZ8Eljrm_AWsS{wE1Pv+ZR^^z5G5aLTEhRZ<2+m6t#Jjzd^aJdv1FP=C8P zLBb8^-xW;r`ETr+J`ac1erwBVJ2%whKhf|bl6<4oi1V|>`QFnu#yRqsj7%#N5_Dl9 zK$TMFiJ4eR)xzYGQdJu^w^#YL?dfPF0a;A1L^SO97p0|@GVC$L4CVgPGlOYPcbp*C zu>=Qa8Nr9j=iYyLd@I8YTFw@$+|OO`9a*+u$7l|~QQl<5HTX4EW?LmFlcJzkH&u z+n#dWd3wgr#;0-RpSHe2qoK?RYD7@DW5syQoKj?E)8EOswrY~5y$0BX4V;Yb{SJle z;s+%9F=gt_IVDLFYTeaY6t(34N;kl)4MzKxN^<3O*wWV41PWXe>rD) zBSq*(aQ_Gtjp_gIZq-r=Hib+a1mli_s%&xOh054U>hr+r_D`zJHDg>j{P&%@k{*^> zkj!w>*q%<_Hzb1W23s2@YNKr4N|rrGlY?eLau>tv=w7SX&}_<$caq0uQgQYBXbs=R z`U*E(s^o@i?nhMgH&nR32wuxt-0pV!Z-K1z;OAwgD-xej*tE}=?9AoIVH;;I!Vlb# z^3+Mpn*lir*(H#L0-3$SeoEf>x}RTG>FaE};-ga$#(0MXrAWIY>PbJU?L^%}>FmzTkw{VE=@O^yE_Y3H`usX5x)yz!dl&Z-WT|R#b=6Fh)Ya*P8=y97mC}8_ zc{LSt5`2z5;$mQ7zq@k+6=K8e7v}p$h6X63&^W)IcAvET@tSRMR~B8UiuPw?EH!A{ zXNxM~`hvQaJ?`${U?Ddp+|2QNF4skn@y;#K9*)xAUguc@FAwHieTA z3Im#kaqJ3qZk$Ke9Lc1hRL}toy2TTy9Qw5V)PNsKfP;Rz8Etoyl4t=ADRe{tQ_kka zgI(uKBf0v5%n>|oa}=|0tCIY;zInu`X$c#3FhEnimZ{qh$M>w4#s#C(z&rUwwqK-p zaB-uT8Zm86Qqmgr$bbEkLnPvZPu*ZxmT%o~(U0)uCSslh=Hf3y%`^z-S|$&7(XqEK zeGZ3gJi?B$y5SksDg?d3lX%9Ssj!W zu>1HTT7n5qCP6E(^s~r`eYZ(hZFxS#z-+PCE9`ym>)@>n>GK(###pFLz|ThfWfEjlZQAHn40ZrP~K1<2XmLRhKRJAs9;hrv<>53*&+l9k@1gZ)I?7uv*crUK-{+MKuH4ZQ? zjRhd))fHXAirPkX#Y}D%5tkA3*a@=`v>V*N3$cBdJ7i3%IP;&SDe*r(;K%fqlP!)O zxiOn1RJQZrRBo`#4IY-8@@wgfb(bB{Z}HWY4&jTn11ZW_)3vua^HIU8-z{aJjhfL9 z5*A(R;)XTd)HCuV#A<3cJC*ydR1YnB%44w=2SIu20N*Ez^V8Oqzf+DH1#dnb*E%?U z#?>8*-t7rSJK#a>Bjv^wsgyD#qI*5NuibDb#Q?Imej*9*kRK)bs8suQA1BmC)lD^> z+T3H2%wax`X@fqZ4SGcsakybyzmTlqB^BC^W5{@|Pfrlo^rfaPEQo-rgrci0XvVfC z9f(%08t>W++&LeZQ&-$%!r{CGu|1_eH7KbTQ)A_UVg2VgTkelk9zI3E|*WgIRRD zOGMCNdt^&w6==kzO z0GpEprlP9yQ$GTT7t5r(4O^0Ao@j7!4mnf~^^05>aG`#?5_#}@aZ2q%3@CgJ=gz9D zwvLf*{$>5}0b=X?s7#NSaGThAn-pS`&wpvBM|1c0q`TIx?E}yTdeS{N)GZYEyNU>o zX&fz!LAmgJ2ad!u?>20#-9RX+V5-;U2UDEJ>!7w(4Aq;q3zcaHSiWWZSerYJ#BcxQ zIxz^88-eTW)K{HaFGSzr0O12Hm39|&ZwL>3cPqHjZ&3v{^cUNh{4Nx(>6(*6ih3{- z>E@QnRGwZt=0Oz5RgR8TAY*0loJHX!C?n9+gGAM1&2_Y%;a{#?9O9y^{t@jY?6_j^zNwhRlA|> zV9Mppo2)2MkEv`FT-21x(Nxc928mnVEI%BzENztbTC%uMZ1w@?WtZ&2>iGzHIykpb zQuN_AbDCo&B=TZTyy=ji-FoXG$*)n@PmQ1ccY!|ZnsK&*!@`0?lj zxFGGb$e#9tZ`vHZLyjSi4@NwTEzQG2x-PY!1VIbz!D8(Y9}JX1N=uHIHz4r+l($2p zhi#)X8flCE@}S+ScvW3V$hI0h{GI;)3j*-WRiL&WfdzIew{4F4!$2(NFC7dTlP2Fj z6P~AD4`EKjm}sng-Je1TDPS_Lr$yb46xexJUZ@K={^tv)yhYk6UQfTjPgNS zo4AsShxFQuVC^$Zi`(Z0^S0t8XIZ|nr^@maY*PC8GNb(feyUSGK={u_$w9c(t zs1id|Z(dTN)_(I3>f3!`E#EQPl-_096WN6kuz<&b(4+DPIQT&%Za-NC)wqktA_57I zETb~QK<*yKoF8lK9uFZq3jcI?(8fChpEdcfQ}d8oxK+oa`l%;jv?4%Z15~F_sXk!A zk&$;llV!fM=wU6e0XpaE1m~~i=WNJ`nyQWT{HbBqxYClv8E5%!d#7P7Y2k-IA5E55 z_sF2dXgpP~_(YA6|0yg58_^}Oe>Z4qMr4`oHkD%BSG=W~Ww-VnlQdXVsB&uk;D2^Z z^%S%iwJ{Tn%d8IXs0dQP%4L0DCTm! z{_S1Rg!N8?9OrifHPpOW+XhaL*b2KAsZN6|YG8LCXp<1QsDq;a$B_Y=2hH*S#^ym% zycaw}VAv*-JSkp_{t$6-tz;ZWrmlS>`Bukdlk_7)cgP+AnF(;E-=M(MW@YbdDSWlo zPs@Bb>A1w`OC=EkE*TKdcf1Bd`?i>4nl|HA=P{#9_Ah1~gry%azJ@BIMmw*-w0ZwL zNz|pqSo`?+|H~$wLM#r_OwiAw{IX_sRIfrgdheL}H^Gv@LD_t%0croR{w#umn5F-4 zQE<`+2^YhsyG_5Io;m1OdD*_B@AQX)5lLQIR@CevTiv*X|NE$Ug$rJYDkim_#di0E z%JA3~hXiVd)tm0@7?{@-6(HXh>FV!*N+&EQA2l0GMZ;+uLzp7tPHjtXkQKtjHZ@daf*^iSD=A@DkY~G7c0A1$g?dw<)?OUXFDqtN^4S(tsvEh&k6iJRFu^m0)T2s=dp;Q!c7UnDQH~6vmU17i z6t-5uSnOtK((DZgR{$Z>Of2mE{XrT`z0~BN>DM(!KvLCoK#Y=p6y@S77rq<8Ej-Ez+ab-G6i$u*7jH+Y4L)G zK}!;FIWm_vrA5D|jJW@w(Ecyq4qo$!;S|9+O4{CDDH$Uucs1Hb z{*et=O`t;hPtoq^RnDlQTEZh*e!Qk#@crm7TevHEk`R{Oa-R20C=|lRGcpq{cEhUpTmfnU>}ZmvSsv!b_T9Jm$4uNuxB58Y!|s`nShC`V_=4ak%P>px63zxeBRsorWN z*yz&UXX`zFpUtElCX2HC!fFG=G4j}M#3E|}I5JiC3G{0bDLFsO{3epO3slX7wV*Fn zGmEU#3QKzL=m}XKJP!%k_eZnWYgo-DQY<;(o|36-A)>^QouhV71wO?7?|aXu`xu0z zD3@5-7z@z0Y&%uqAL$L^qVN=sH?5iyhx9*P${I|N;1&0*O5Re_zWy{eSNLp~xR@l| z31ZOKCHDDiPzFjtoZ}5djNA3Q*+$~wRQ5}%Elc9i``mL(&j&m?%TB0Dym9V(&AC)5 zRU0T;E;J{U3u1vwO^}SQ2fMkHEmLLaLf~`eB*X7B(nL`(^=yff#K>%$$gQ^u+y?sL{H8_q&(y*RjVTEM^BT{RV~TK_EXF zJU(`|4Q*GgoF~nTzW(9Lc^M`6BtZo~uuYx`xz3fjV3-jr2JWf1YCJo?XYOUkT>qyP zJ!-jz8CK)5vWZ;xd3*{w&1BpIMq`b~4Uw0aW)OjI;;x=TR_feEGs@z(OS7C2RbUei zv32|A`%RIb#puw#QBB^|D5Q@l|0L%K(*U{t#>EE9BGR9I*Zrh7Wxu; zYN6em3LFavn0V+i_uax_qs*%5V2D!Ss6*OW@tl8VamJ*^`R(ZzO=Mt$1KKOyDU);N zL112H%#Wb2S(M}=r3_@LgWKsvDKnP@bMZqNixwOwAW!UK-bSRksuoI{IJK@9xs_OT zh8R4(ol9@(_YHh&w`18k<*JG3;0^UtP0(VFWNgpS43b~VF7I^*^w7?^n0iwhJP%rK zwFiK{GD~yyeuwwr{aIXv@1#Mgk&UnW$-fZ#Pxq*Qz~NAVW2!Chk)80WAm()|tX5h! zAd9J{leF!7q&^wGPslqfdV>`uOJQfBGQ>F2A*KOj!Rg||V*P=U)0_1$E;Wu+dPtAj zLb;+1cu-9DQkkinnRStPf&o%?Jh#Yt!d2jVe-R!Pdv#mb;CbI^pCevjFms5_YLvLK z2^^|>(9roWb4<>sG%03BgA_dPl`Cj5sLkD7a>355KDFi2!u!K~01o_*1)M({9O(C9 zF)TfV6YxVM!8>O6tu4}TwRe^}wgp?H97-@o)u>ouRhax17*NKfjSb%h2p*RjGkm${ zYJT}2{rd@}kJg{{8K_0SX6F=`y=o~qo?R4j`V8WLNvWsV%pIdteWigTQoNUX?NZae z$)sK@5$yQW56S9_m5D5J4J|e&GpER>5=KMi#fWdslcf$OHzT=a2DOdGgAM_1V&EdR zGb_NsB;rGTgyGW%l`$x#-i{8<#e#Qvm4`YXEuU2^2++x5=kAy-4Ky6^^8g@(o)3_IfbV_(v5VKQSIBZ>=6MCdoz)1DruLm4Gm!{vPzXMLS4a` zlUcME3iraSEY=T!&~tpC_}t>v%~{JW`{Y?OPsxF=JE{|Es+(kB55w`yQnv zMTfE!Np{(er7}dRWE;v(S;7#7EJJoJbV6kxhOuPJE?FXL9oZ>Dk`@^nlI%Or=bFy> z{qEm=-_LzNujl#W`RDGB)9ciPxxUw2*XQ%zJ{PXa@<_yokS&n z@EqU8fx-S(BK-ulYi{&AM|A>f4BzWWVUa_Is*@`R%7Y*!IyF-Lh#$71{gh4TSd5Su zHC8H`Y?jYo>Ij z-&uXQ%tf3YPyEJZHvI$KN8`9A3!!|CwfXqU!u;%IHbPMP=e9MEOE0`)4nmGY3@z%;Zx_6y^>FF*eNr?Q1h6yWE)E({X;nc>1hs{WH(J^Y3su z9FEa{(cq`nJ^f>D!<_Y~uvN~>$aemsW3w6r9p;hQ{ch&Dk$_A`o)q(M9;wWJBskmR z-dxRkhC&eh+&NQJcN)4{l7v-GhvDnP|0wB$BcX%yR}lHFB>Z9JE>p8S*!1nrKFv{E zA8H?%lRIH`58i*|c*M0P{gx7x13{F1aY16e#8xqC{&10eMMt?xMTcn53^{T&qcyWE zr_+Pc$?X&UK~p2@98=xE(N(#@L%f=-m9IdoEiO$^9&AqiKiBVL~lN>YxMY&ma zhp%U`%FmlDG5GOH$-3me#kQMQR`xa*J*&ixk}VBoPbfAhh&(b zy9H6-yd}zkvE6M?gnnn&TwA$jT(E=W5z|M7c9j67=ZShm-JZR6dvrLa$!y2WC02oq zVg80Q6jK62*s=KHHf5{1u(L){pvKPHeYHpo8(>KyU5;Rczu_D2@M zG1J!8J$r@hg-y$Mi3ZD-4mRh*8->|+*)VkdR(F62(Y$uUfY~-%QE~XqkIGTM7t!=W zj2HEC15lq_#Ft&&N3SZn+SPDsqNw_|^1`*Y9A;Bik@cT)`J< z*4D;Uq`Y$VbKZ=j#%Q2gh)zv4mg@G&ncJzPhN!w1s&bEBSoCg}6mCd8;YP-Crw5_# zXhBhWOVf|iB+TeV(l)%v@Xsw(y3@TyFPJA^Fg+j73=c8ioOw>bI&RC0D6;k zf-On1b{FGowYuBgv-lFTwb&vJ4ClM~BrwtnAcwpCJzm#kHt|sUy;@P3Pwgt1O_Mqb zoazi!L+Z`HO}-Z%`+Vr8CUDWpo~0LcFtZXJTs;pBR&raLPui0SM#kxVWmVuS2%QR8 zd`|JF!(_3Q)CbL6wL8W$*_l@#x5niA*aWPR8Y)>c~ZI{T)J=vA#u?E_o1lL>lL~wPCfs-kD_K>yY zLB*FU*(AZ!N?YNj{GblDFHB6g6-e3TTMeBx2XeZHBIup_ph7nPL#2`%$OSUwX1Dm! z*TogEF5+p?%^y=6&TxdDi-{Ff!2dyk&=>z+iNs?Z56O-RNI}NkBGQH*BYR0M0I)g? zf9s-fZ6wDCh*^4~L|H?q;gF*z%F0kkF^J}dAQt?yNPX3N zNIq}QlK$ag<&E#NT`G0lL;MXmH5u%}JDV_?5CBP-hqhoaRvsuMZQ$pt=lD}OiU)cA zq~Tk@lSYA`9~mezP4AMDwo)EU<|gJq-p@G=P;iGa}fI(KJiE6et2r$m!

kyz%XJU00yocP@f90*mC$X(2}KC< zuS&0=6tZz{SnU@ObSHiSQqa_txh`{kIrLId1yXvz_yW;vU3)qqNbQ0b?WdT+?-(=E ziW3)&lQcIow>E&-4%yzy>0dxSXYgPx7y~3|)L#`vD z5P!=98$aGiTlMu2@#6FC>}I9=g9n9dTkKhb>A?i zFeAVK06;7FkXAEQy9%*hyT<2>0{trsXjhi#-&fYYzbu+q9_@MSU#bnJ^gclESeyMyIE#H%d?|^jHlQ z*ND`uI9T0yZdLXxCqphN!6n_t$brSVv>pJAe^_^8M7OocWJfvFRKrVOAJW(z$JRBI zD{TJ z|1a!k{evemZUJWKwfBHawd*t}Z|umFI@k>RP_Ee%0PopRnBNmON~<*-KT=Z_j=i~V zrbA$o?=7$oWJbm~uJ`jR3GFsnTtkn2zXWr%O1tbi;8c2n;9QC~-+zZ-y?f%%7!3#&OiW5!&hp2Pu8g)nzP&@-YTs7L~Q1*fteh|I9%PWDoOS95Tj{cUtOA#?F7GdBZw4xP5k;y8V0x+NT)4 z*UxSMlpl)n)W){9!dXUaJKQjK{04BaZIqu7bqJ<48?wnu?C*_OK3lFcG_-$IRr-bA z&4}AyAvUj?R8s!E_y;^OJpS~HEQmv@%2(&|dMZJ5jTTZ-kk(l06%E%Yj@t7?-s)cd z0i?q6KL3=Om1v}&NW9(bioG6Mom^dLF(O+D8jtXf29J@l5qV14{0sCgpM(&$1i2P3 z#Y(-X%kO-7E7sBkS^P>8PD^!jXfT)Bui%9`1iSB^4OmAi9RSNp++&0|L2q;26{%c! za7OrYmt;w2A~g_gn`a!w-yVk1h)vIfpo;&uf0(kMr!v#s#aF2%GL z9trl?p^k<{j`HO~jSO8K}U8)B9GwWbNrCR@PoS z-@@g6ge;0oqId=JeN^Z6s#ldSZVZ>GDk$&#o0N@~r#Mv;1POl^W5Wn;j16&eQmHGVt z1FD}IwGFF&+!ZzVbf-M`g45|!oT3VXh1!1e*d9B2fOq}dxiHv;6-u82fZb@VW85vX zj)V_{wgfWx3VF0j*g^zsH6KUuWCO2SaqVE#?Q!bN4O(E`sJ?@9^zF$~VO_SEKAEvQ z+XuL=p1={kGh}=3@C-a@cVc~J>uncMP@>Hy{Z=uq2d|LnS7mst>(?r#+|gj%BlEC& z^bX%Ids}|w*y8lV!0KJbtPzy?(U`GVjOdp(4+2|RRSWK*u%Q6Nm_1E#7W}6EEM{>Y z8j{?s1<4|*G>hjEd5j60$J-h%SIltn8rM{pTf_}O8kRPdQZ}H2K?$+18<491$6hj_ z&$JO05yQTpRxPICzNyLI)7;>idwhFju}~~8HGJXUS}+zlNjREVrOy3 zqlzDiK0VPNfbv%;S630ASUcryskb{;**@!`YOH3W0Ts|{!A5ju&UlZ+tRnofx1cZX zw;pkLfjLza#d_MoNk2h?($k+N<58IiyPmE_jJq=Y31nIuA zxD_JTNcC{+w@~U(==K{1EkSNpeq_Yq#RD1cwFYNK>K7nwqaZ6^B9^gIxnkm+m~D5s zpnpR~f_~ndY1Ei<&0(8&C~~yKwEc11o$N}(zRbR{lzPD_eYH#DMkHnYkmK$;NAIHU zxtv7SRXMg+cTM~Sl^N_fv%BoWISqOUB!wK)cWn(eq+t(i*?|Yg87dWSbpC7u^ObPF zf?x)xfOzvwr(7&{o-6A1*jP$n?bI6!kjFjPd+}~izB!1j@MUL6N*!g@S#yMv*O@Jy z;1H=DUJRI84DIs&;ZC7?1ulBE1sItf37Xk>`{hE#IAH?y!mE#$o6jlhCCkhaR{UD# zpOy{j#_HWF4an0i*~n_qBV}f?W|e7hVWLl|{Em6aXM6K#H_2f8}&0yhlz#M$Y*c90m9Ls9))+Ymt92T|9A$V*T`KJnCC4rtVd#-H zd@*uTLGfgzd6AnHnJ3$z)pjgrNR&?tTR)lEaz0logs%cP*7I>!8nppq9w>Fb|)D^Ip<4kJSOHyP%-}SDHO%_oABW z10C{MR^5GW@kLpB*N~(K-1&i&BbE-nts)uF&JND-by-AgyuzO@YtQd#FmlGOX21yl}5}mOK@cE$7>|LyL=1l+KLQOgTzD1Do=` z$2UJ{j&iN&;!hH`X^lLWT;1|(-QKRpWwe|<=KsfAMNvi_GXg09swU!kbcUm~&$e?f zLgsR!R!2Q+)TN_$hS1aG(GVM>u%`}c=GNReXO@K?520Q*YE!xfBR#89#(tal^)lOm zjAa8XeX>TJZ-t>e_2*H=9LBHRyYdV_4vK%Gq7+KEq1Ji#Ar)M1Qny@=dG>Qmctu~3 z;40%5_Mk5iI(2~Y5QwR=W{fuxp@tVuqlz5Op7VF5S%{OOl+9R40wU^xT!c?NB4K^$%rQJ^{LKN!=D7SLro)m+Bcp zkxcY^qTrO}ierNEcfyOP*-pixeC<~Eb&kzp%e{4*t96!Z$S@8SW_7yLA5r5(@zuHP zs6}P}T!FO~(FU#Oe2s|_JIVP6U`LC@PzHTkORKXn;VA}beGPRxVGUSIPBI`Np6kZ) z7?Gh>VULdeV+G0~f2cfagO)6FQKaoKi{TGv=TBr&gd|c+mgievHgq5|b1Sco z)xPl@>MDhJ9~GHT)qPc_eI6kBnVh-@3$qVU#Pk+~2&dT`D^cFHzq3^7+I*sq{U?XPEoClSCD}aNETrN3jl?1b`Rh+2N0mmwbKi zbe>55Vz7!DHp;99TfW7XxH&@f){r~0vdd7>n6IJwgEz0GUoofq+7ZK34qN0M^jhMq zr1o7{reG&5`j+L+YMnKbO22o%gnrl|lqhtaQGrb+(=y-Ss{b%siU_eOL$iLSfhC|A z8f2baQT>tvO}3Uh?q4NS?n7rFOsWkk?Q8+66<*ErYRD=b4!R+U32Y(`IK1VIqfM$d%cYJQvN6LwmDK@gm0{< zoiCty{T0@3&~V02uEg^cWc5g#_J?X&3ZW6~mMh)FR-x1!$CfbyYcHLanD9mTZ*IUV z^B0*=boie5k+s+m<#bwtE{rdUE(Cmd+II~2=CNiy*Sh8S$^6+qhu3qHJi0X~Hv>FB z%euYfvQPTmhQijN-g8%F>9&f<0_YY?wm8<3_H|aoEDLMLTl`3|V=xs`_?*j<{|dY< z)Q($vv2k5CLmt*qJ4~`vUsd+hOnLQt7mw3#ne-VITAb=tkD=sV0HRy|QNA zfm$n*mC!!7D&xCZ=-q|W^)nsXX(70mtGKV&)l`GWG*2{Ixa|<1zH(9Y3>WTP>g1L< z?UXw~f;=ACyvcLMZ^c&Ga)Ks2TK~9}{=^Usl?}SH0 zl2uPJopk9=aKHxW-0h8CX(M23aPp^fO(Y>9*>QZS?bQ4n*e%;#KY$iaP&oO9wdoUC zM%G7fiwHh!X_t=av!&6C4>X~Cq0=Zx-s?t9IFOM#96>)aa@5uI0G2>W;5afjmmw8- zQNj@9h!<=_Np%m{kBW9{=bIPDWj$D)wob~FdSt+^bBl_*gPm{aE?hpJ@%1Naom=Z@ z<(>NDyA6ONd1U_xJIr8-l3WyaEoZdsp+f$fYz|Bj*oz#!t|$hx|3Mx1`Rt;{)Kgk7 zh)+AQSMzVj8C%cEqH6ud`2mch0@@JhdY$*lRg~ueVr9 zhgl9#m)gda#7#smx{CXQo_OFGm>NLWZG|4^g7h1qBpgjiR-xm9g6{c^{N84mq$j3%?70zY%c+~ zDCZ85p-gk#+G7DHnL}58XEc*(?e<_a zLs+n-iIY~Y2BiGwRn|!inJmwEP6*P=Z$wYWQ5n#OB0dq{ySw{iS5vdaTdq4 zcK*5-)jy!MFN=QGES*Vx&CDL!N2npry8G8X?(%XUMCPYzKbj^Vt3~xOf3M7;yzt~< zY}b^_#;8lK=*!si>4TjoyghB`Pv(~JlF+vk9hQ{XeMEe{lS zxjjxau{RSlg-8lE+@0~JEB+rWcDsEtLwHyyjN2$ZC@`OQ&-;x2Y>UnX&-U(D%DocQ zU1$a7tHHKo*6!h7(etQ()k9HYe3m-PKNc*`G!}$46i1dFLQahw(^5CF*1+8Hqv{S* z6bmp+BgghW>K*URYx_xsFnRoR{ZTtesphHZvGNPuQl2~%4vG%Qn%h}sm5X5EYr_@zUavwRroYT zwZ^XeE+gkeIxARO)%z-4`{xj`fL}t#Iuq_=sE7?RlK6p-Wgz2}S%j&%^=KC!z zQ4BvtwBz)snAPPk>J+stVe}GcvVv(@lxD0!vA}hm^GW0VRRgXd9`_*~mX%$N#*k?v zj5z7wi^vYsi_S+hIS9oGoylR>V5rYI{3{j*T_4`h@3_;<7vRFE!M^={qGW2j^wUP* zFqYxC_2}Rb*Dl|cmRz#paTuGptIXn|rPepbMJb3uU+-E6I(()%U31(I)e3m zIRCOL*mBfcY%BwoDfr^3APVX{2c9^I85LH~w3gV6^bKb4#vt!qbzrVl)j%yFZ;)`< zP~Q6oZvKE=_!t&-z(gvDl6$<;z3FQ6aVgxV4>HlYcez27%|1y2LkE8J%|Xdm0xch4P%8N zMNhgIUZ3D&VhWWq2kz!&E!RmGppkNOq}nh0O|Qs6ns*?Rw@TN>;o|d*rX~?Hjnpyw?Rw@8vCid8KR}1!<>Z9B<~k;So*nsrX+4{b(Q`mHTUbw2ziq^ zD1b0ibyb2n)-)di+Y+naap5*)b%`#$LJgE*VwrL|_doV#l?~3S! z5+Z_)muDdEGwsR4o#Cu7cq>-wtYtU^c6J5u21Nurgv>X7W8(NVGfM4#M6kkFm>`7- za)khUo|n5$(@VfhUk32kzef|PzDsFal7RDp*-h>X`)*zkv`tID99X$LLyzG+vG5xm z_G0O^ff8cgZ1Dr4WpZM#P2oWX_T0;U+yBFwJ}*N7a9Tvk0`!I<84v|77}Y8Vh4;OoaQ2b+i*k2(W_t8gmWvLh}l%a;ufXi!VobcY1!xN-m1n#)(&Dx05H* z{ZSmOffkba;4$==KrLUG6Lsv{f3$~hBnQ=6~0 zTpcglN#i}x2D_Z_&|hXXu>Sa-lGm}zOWlbRQl3`F(YO<9WGs`sPpmGiu61?-NCP;| zp0=w`b@DHhjM&4ewsvh?YPF9|8Vl*qM&tv!HQY+`D{UJh03_e< zrn6UpedXirRL(YQ&-dMCpknK*dlA9O;xSSUePlY}>&X;{W!Fzv4b_@x}s&{J>$SnH4gs-X==)%|l3`2EeW2~`->5!ve%|cm`(}7Y{>m`1J zMEo>jC7+!-r+*?0TVfEH_5{mI=PjspEc7+cp2V|ZUX6eL17o>fy{_QxVbxN-G zKSLrFG(qVIQe@J?76_BJdrGx|NWB<}@LJ09N*R;cip{6pR-UwxgzxK9nX%teOLE!) z5qmvf{N$?$UmFNGEX_&UD2LEn^K2KymKx#6+R^(w_` zNX=wec!TkQ1h@u=b(zIrW)FS_+}(pdS6l%B*+=un5oWHHvHu^fFcvArIQm?l570tfnq zgUp!YXw1AwpGG<4M#cml1MJ)y>fmdNFWR5AExrVz*jNdiZ1~w!Bv&}T^!avM)@jC6 zA;T>ph)}I6Rl60p%UsF%Abbx5h?$L*0$n|`^7s6!w%O?1p^cdJBdF8hG@b9f8Qt?Z znPl?tV%hV6a(%@~=VR-o5wuB1NPU3XzQ!J16?0f9hmD_rg2csg({NA}Q`E@2@r5^z z?`1}FGZuZi26m06Y|aK+7+tqj|LSpTRXA`)7;h;NEwVmWy6UHd#)(0=Xr9*BufU`l zO(Q%)$$xY8la!)z=dzzNYcYzvmPqy)&l&Gy(tSSpV-Ed5{Z#t;>oCrU(}C;JF$UbP z)IB%+Tx#7)9%LDgx@=^j?0R#W*GFB8&@9c^J?)xd{RL^@Sc*3X16N(}P1$J97cBvx ziQ8SKGhuCw`WYl@UsLSV>zD&sagov{5&<+Yh-1QGN+pK>2zjV>RK7?Q z=?olqMm}(X;&!2A`EB6l)Mm{y=-^wob|BQKSK^iNO6ENp`xZ?BQc&z5WMvG)xH54V zYPOMc1Gajrjw|HQzMS#=S_rfBj+f@w+A5}BaI+9;Ie-N?nQoc~4(p~BewKp^{35Fk8ZW_M)dM8m_} znun1on1mI?HFs-^oL0rSw zC(l|)lZX(3&UD;~@z8DFg0Zneg0Dms+h?Qnj3@_Zz#bRk*11C)xgA~MTs5pX=t4Nx zj7C%~^!?6&4vTv3(zoEEyVD}fNl}zfw^6qiK8S*|q$RJqP0(jEi?|Ilzhl}9&u0a#PVu+Dx?W``U93?qwy0D5fTmMR5;lztU z)VubG)ila54uIg&INWW-&?qpOu;)VWlz!w~@dA$3Z59nYREEZ5T+j+nIRA5>Kt*n!2p{;f(1eopnD>weOr`XV$ z?6+VTw~B$)8*NEtu>GB;I;Yj}@JDJXqpD~%{;9bGg&_t~>6zfSlUx72An#xP^6!QQ zlub#mFMG0I+!Q!NAn6P(&Gc<`u;U>&p^MIe4OFnG&^#F|=N5uy>EpKm7j$UmyZY}{#l|z{9p;zf|;!-mo ziaNbDaU~Sn1xKQZ^XklU^CWBSHF%PgQqbJheXvl1Ah6yf5{y!a=ME!^o6Zn>y)oZ3 z1NL>^LWx9)+-X?(O_=)89xcS|NlPN+4IWixv_yt2T8cc1yK;1# zW!LL{fw)x;2!%48Mr!5)I~G*MJOK&iJ}{MPKz{SuBhvfo;PWj|jwbG<)&EEyvV?q9 z;|-2tzRGiHEU~dnmLLcULpJ&( z;qm(LM5m4whW2!|-EKKn{bhG=%rpT+cC;4EJL0|B?ue~aCdp^1*ACA>M0Obos{m2w zMZ<`O;!>?aLrT!yI!>C7+cSZ3oR1{v^~SuO2jp8)S0UxI4J?Exs?@DPI+^!l8wOG> zFw&lG=VM=gK}6lEbv(Vp{#eTh#=+N8%J9eh)*O1!!tjzo_*PyK9j`9FqdJTy}xd zOgL-+Zd0yjRk20p_{P%LFmz*+WRwwTRAQOD5|b)#c&VVrOqW_D($SWs+vSHzHOAI% zOSNap{5t5Vw~%7_ZMMy$j(l$6(IB&gO*}bC$e_7l7{#?bh|yaNzQC)UouE$Io!QfM znR@h3F1w}*?{!lZUg!v9t|b0I)#U)!_1vTod|Eym^{x>AcM!>WmL%JHXR1Uwr??bB z!%h021I;3@NaDE~@b7bfiy1HW2pzoR+|LLzS;Lhf8!i04+}-#;#p2#03d~b2N%p23%?s%9gb%&ll(Y5*03@>`i>(EYh1P44LXFF zLv|)<{eBw9qq0si?%P~&*(_hXlmwSMUN&u4Pm%+5Z3#6DT+cGSO|t}#cUt1^0#pHa z`;$mMvVLZS<*4w57q{1TCP(m*H%9xH;S%&U(e~Q^SNGgNwz+2GyLaf>f`io(n|2JlW7z-GG_sVbm9eqDGh! z(V=i%35`L$-Kh~_cOhrI5@}_&j&q+>0doK?uxOzsa`vm_>FwEi>4QDb=KG`V&O0a* z?cAvs1$&*iJ}Sj3(t`0v&*Xg9{2BG8ne_}!5AU)Ga}*rJS99sCoWDhH3rd#wQ`vMT z&m+Vs0LYq9+9V*XA5-S$eR>gx!_h%GWp$DCyA>!HnW>BV{@SHa_pI3F;7=gNt1UM! zsXJWcVS=)$bIp8TnvtT!9m3(4H^ypyEXd_&+0MQZ)(V;KTw~2og5C@-T+Ya&KM=i0 z0YBRjl_LppR!4hvO=;?8zuCl3;)&c@m4?oV_#U)h{9!UPZNCH~>A8~nbBM#q6^$0G zYi@$;L~p|>*764%b|feuub@0POD8#>Um>XX(<@u`o>Z0f9uALz)&qV!(AYjYEAh9`g)THjwLU^c-m+JfTy)Or)r2#5x!u$U&f&z^ z^(yO^+UyVS(}q;T=fjaMA2Ub@koJrHfR^x<|QF74$b&Q6U_*7>GlIFUi9|UdE zLY7TRt|5B9aOF4aQV4d$;Xj>GI03gJ9zJKx`_Xq4^Y7@4EbT7g|dK7?}ga z1bJ%IJuxDicliU$KN<@dmgIfdmWzdHItZJ-gEss6oJot+GM9s$WQc@?2%nb4q91dQ zxUK2wW*L-?sQ~q0(C^VgEDp*st+Bgolu~ZK^ouX;XTpes8v^R!pzZ%%cL6W@Px=xo z9SO72$5;l8w8cLwI%$Ldf^7J^k?{HNxdo7*L(yXO4f0c;(5Ay68xA1N-aOtXyv=Kx z8~V@)XM}`6EldO@_}dA(5*jJ7dx9SG)d~%Su_)}^Dt*@?!24&%)W5ihhG9@kPL_u- zPqHLTv9FS=vVh6tH;LhD-C7lYsFCy~(cJ^WS$D|6DP6Eo#=_x7R`cN1YN-a&@S9Bsh4~KulO83J^6P z?@~M9WI#UPgywa1&i`r%S_O*Oju_>O7!{ZqUVL_5bP#BLSOke8;VQj@Azva{jX)+)8UCkR1f2oD&?XM+lU8!c{1=q2Wuh zut@w%4G)hI`kDIkKzZi_n(d=-V|<>texbYV>Vd_rIv|#ZmrnkA5n8z9L|R@B(G@P{He0WLc8t|c$i-pZ0vahri>j29b6b14&S?1r~}g0jS$Y) zD~RD_hWEzr1Z;_<^7K93y{H7H;TSpD$zo6e<_#l_@JFbejVr99&>K4rk-%YIH52Bv zgnAsl8#$V30Jq=I+q~kazjPGC$Xx|r&_VD8(Z!!e#-LHJ|0Hw|J7^sy5lZvMk-Xp; zv7^BcXt)tOEI zo$n!`TSIssYLmQ|5tIR_-c;<^T|8et8zT=30K?D;!x<-!pbaCcM=kzxCql` z$ek7a4zMW{Lqs`I~I`GjU+q@j>mZM`#{X?^{tcpnkwZgl|{0@V>(i>@qq2#faQ)gnrk+oHPK!8{s!JOG$r$fdMttgW=B&U|!RsA7cWD3aN9P zts<(EZ@;u6WP1beHj=uWZMfpW)`rtOSriFIm1^jT3A_-Xr)Nj?~mafz|pOafSmEath9& zX3b639K8WB-i0BSiiD*g1K5CNTgHR^QIyM^U*!=LJ4^%QLH3bLdXZ5H&~li*jzZ?w zh^fDpZ4z0_&vE_oy+S#PXG=rGPa`}xQ`D^gB7EH3xFO5rSDEb;3tPx#_3yqP#oe{!`-RS1Ko_&Fc!EiokEc*V(I-YP4Q&j>j z5<4(=7Skiqx<>j~t?1afhceo5i|V?<@xXzO0|f)8Tw@uz2?PTUS>He#YUb^IgMs#7 zx%AdQG`Y-$(;v6TBBl|0>?vq3KEyci6|&7{4if&ZTddJZvl=m8@$|=?yxW|=;y2Y2 zG}$q)RJX)|%ka^01mi-oXATKl=?qkVNhNq$-9g*=|8S7E=bQ~=Hyp!YUWG+~X%mx5 z2irVk945Ktbl%U7Jz{J$W_PT!?Gdrx?k{s4UHvmuSsF-H|5Qcu%8ZDwv@DD*EGuZl z$%iE%h)rpn`IBFMBh0QL5Xu_SQrtlzlqZ@lQN&*)T$IP5@XiOu?}efCQn)^6%w|YF zCr*~@Xw&XUj1{*yvNX2=^fLOoO^T&IXKxBfDC82``xxXf*KI{PVtl+3UMrhz1J-1IL&EQ(|G0(qLFB^&omMnnkQp@x^HL8 zQwAQ``I08bMS5%*p)5L?Li1n4IZjzdk(c-!Ma)o}r|e)?^Mc%Cvlq{6#AqxNdcHhi8q|(^QC@px2RC#$ zR^YF|w5JENmVjMz0OmZ0g7Qv{R{|re{%i&e`|J;uX4K^~19969!27p@Yhd)vvtaSE z-ACW0ir=wHG`3#!EXGE4iwH)Z-$X-iY_5W^LhziahkO!bY+L;m z0HSuKXtD=wd!-|d!OaeBk%m#c7@N*c>3ZAQ6BubP^!?BGDE{eLOeYjQ#THbF-t->N za5|x|N#*(v@bAUXzkH`q{&Kf8m)~7XI}5ynJ5Qo_Nq@XHMmPEpeondreGf`_t_Mwz z_-?fd0J?zvxMDlT38{#4ZyshD>Lhu;$nD) zYJXn(u0EKsopZPQla2*4=CvYxj~xkNJRd9HxiX}xxR>2uIDvW13xE1YLLSej%J-2B zR-W#axbjH`&~w38?4U4Tud#8-K49hC(X`MHgNxARTy>OJaSapwrzg8rDPl3+`b`B@ z9Nh6%7*kd8x;%Y$ed*tJIFBp>2C}$=Hh>T#rANW?zdN-(mA6&}LxL6IWb=%n`xZR4 zJ7^TX(yKps>@ZbdR<~FkM~nIDfhp1OP9%)RS)DV5s#drD7s#^NvH$=8 literal 0 HcmV?d00001 diff --git a/examples/custom_keypad/src/keypad.rs b/examples/custom_keypad/src/keypad.rs new file mode 100644 index 00000000000..81800f47d84 --- /dev/null +++ b/examples/custom_keypad/src/keypad.rs @@ -0,0 +1,255 @@ +use eframe::egui::{self, pos2, vec2, Button, Ui, Vec2}; + +#[derive(Clone, Copy, Debug, Default, PartialEq)] +enum Transition { + #[default] + None, + CloseOnNextFrame, + CloseImmediately, +} + +#[derive(Clone, Debug)] +struct State { + open: bool, + closable: bool, + close_on_next_frame: bool, + start_pos: egui::Pos2, + focus: Option, + events: Option>, +} + +impl State { + fn new() -> Self { + Self { + open: false, + closable: false, + close_on_next_frame: false, + start_pos: pos2(100.0, 100.0), + focus: None, + events: None, + } + } + + fn queue_char(&mut self, c: char) { + let events = self.events.get_or_insert(vec![]); + if let Some(key) = egui::Key::from_name(&c.to_string()) { + events.push(egui::Event::Key { + key, + physical_key: Some(key), + pressed: true, + repeat: false, + modifiers: Default::default(), + }); + } + events.push(egui::Event::Text(c.to_string())); + } + + fn queue_key(&mut self, key: egui::Key) { + let events = self.events.get_or_insert(vec![]); + events.push(egui::Event::Key { + key, + physical_key: Some(key), + pressed: true, + repeat: false, + modifiers: Default::default(), + }); + } +} + +impl Default for State { + fn default() -> Self { + Self::new() + } +} + +/// A simple keypad widget. +pub struct Keypad { + id: egui::Id, +} + +impl Keypad { + pub fn new() -> Self { + Self { + id: egui::Id::new("keypad"), + } + } + + pub fn bump_events(&self, ctx: &egui::Context, raw_input: &mut egui::RawInput) { + let events = ctx.memory_mut(|m| { + m.data + .get_temp_mut_or_default::(self.id) + .events + .take() + }); + if let Some(mut events) = events { + events.append(&mut raw_input.events); + raw_input.events = events; + } + } + + fn buttons(ui: &mut Ui, state: &mut State) -> Transition { + let mut trans = Transition::None; + ui.vertical(|ui| { + let window_margin = ui.spacing().window_margin; + let size_1x1 = vec2(32.0, 26.0); + let _size_1x2 = vec2(32.0, 52.0 + window_margin.top); + let _size_2x1 = vec2(64.0 + window_margin.left, 26.0); + + ui.spacing_mut().item_spacing = Vec2::splat(window_margin.left); + + ui.horizontal(|ui| { + if ui.add_sized(size_1x1, Button::new("1")).clicked() { + state.queue_char('1'); + } + if ui.add_sized(size_1x1, Button::new("2")).clicked() { + state.queue_char('2'); + } + if ui.add_sized(size_1x1, Button::new("3")).clicked() { + state.queue_char('3'); + } + if ui.add_sized(size_1x1, Button::new("⏮")).clicked() { + state.queue_key(egui::Key::Home); + } + if ui.add_sized(size_1x1, Button::new("🔙")).clicked() { + state.queue_key(egui::Key::Backspace); + } + }); + ui.horizontal(|ui| { + if ui.add_sized(size_1x1, Button::new("4")).clicked() { + state.queue_char('4'); + } + if ui.add_sized(size_1x1, Button::new("5")).clicked() { + state.queue_char('5'); + } + if ui.add_sized(size_1x1, Button::new("6")).clicked() { + state.queue_char('6'); + } + if ui.add_sized(size_1x1, Button::new("⏭")).clicked() { + state.queue_key(egui::Key::End); + } + if ui.add_sized(size_1x1, Button::new("⎆")).clicked() { + state.queue_key(egui::Key::Enter); + trans = Transition::CloseOnNextFrame; + } + }); + ui.horizontal(|ui| { + if ui.add_sized(size_1x1, Button::new("7")).clicked() { + state.queue_char('7'); + } + if ui.add_sized(size_1x1, Button::new("8")).clicked() { + state.queue_char('8'); + } + if ui.add_sized(size_1x1, Button::new("9")).clicked() { + state.queue_char('9'); + } + if ui.add_sized(size_1x1, Button::new("⏶")).clicked() { + state.queue_key(egui::Key::ArrowUp); + } + if ui.add_sized(size_1x1, Button::new("⌨")).clicked() { + trans = Transition::CloseImmediately; + } + }); + ui.horizontal(|ui| { + if ui.add_sized(size_1x1, Button::new("0")).clicked() { + state.queue_char('0'); + } + if ui.add_sized(size_1x1, Button::new(".")).clicked() { + state.queue_char('.'); + } + if ui.add_sized(size_1x1, Button::new("⏴")).clicked() { + state.queue_key(egui::Key::ArrowLeft); + } + if ui.add_sized(size_1x1, Button::new("⏷")).clicked() { + state.queue_key(egui::Key::ArrowDown); + } + if ui.add_sized(size_1x1, Button::new("⏵")).clicked() { + state.queue_key(egui::Key::ArrowRight); + } + }); + }); + + trans + } + + pub fn show(&self, ctx: &egui::Context) { + let (focus, mut state) = ctx.memory(|m| { + ( + m.focus(), + m.data.get_temp::(self.id).unwrap_or_default(), + ) + }); + + let mut is_first_show = false; + if ctx.wants_keyboard_input() && state.focus != focus { + let y = ctx.style().spacing.interact_size.y * 1.25; + state.open = true; + state.start_pos = ctx.input(|i| { + i.pointer + .hover_pos() + .map_or(pos2(100.0, 100.0), |p| p + vec2(0.0, y)) + }); + state.focus = focus; + is_first_show = true; + } + + if state.close_on_next_frame { + state.open = false; + state.close_on_next_frame = false; + state.focus = None; + } + + let mut open = state.open; + + let win = egui::Window::new("⌨ Keypad"); + let win = if is_first_show { + win.current_pos(state.start_pos) + } else { + win.default_pos(state.start_pos) + }; + let resp = win + .movable(true) + .resizable(false) + .open(&mut open) + .show(ctx, |ui| Self::buttons(ui, &mut state)); + + state.open = open; + + if let Some(resp) = resp { + match resp.inner { + Some(Transition::CloseOnNextFrame) => { + state.close_on_next_frame = true; + } + Some(Transition::CloseImmediately) => { + state.open = false; + state.focus = None; + } + _ => {} + } + if !state.closable && resp.response.hovered() { + state.closable = true; + } + if state.closable && resp.response.clicked_elsewhere() { + state.open = false; + state.closable = false; + state.focus = None; + } + if is_first_show { + ctx.move_to_top(resp.response.layer_id); + } + } + + if let (true, Some(focus)) = (state.open, state.focus) { + ctx.memory_mut(|m| { + m.request_focus(focus); + }); + } + + ctx.memory_mut(|m| m.data.insert_temp(self.id, state)); + } +} + +impl Default for Keypad { + fn default() -> Self { + Self::new() + } +} diff --git a/examples/custom_keypad/src/main.rs b/examples/custom_keypad/src/main.rs new file mode 100644 index 00000000000..5cb26240cbd --- /dev/null +++ b/examples/custom_keypad/src/main.rs @@ -0,0 +1,68 @@ +// #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +use eframe::egui; + +mod keypad; +use keypad::Keypad; + +fn main() -> Result<(), eframe::Error> { + env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`). + let options = eframe::NativeOptions { + viewport: egui::ViewportBuilder::default().with_inner_size([640.0, 480.0]), + ..Default::default() + }; + eframe::run_native( + "Custom Keypad App", + options, + Box::new(|cc| { + // Use the dark theme + cc.egui_ctx.set_visuals(egui::Visuals::dark()); + // This gives us image support: + egui_extras::install_image_loaders(&cc.egui_ctx); + + Box::::default() + }), + ) +} + +struct MyApp { + name: String, + age: u32, + keypad: Keypad, +} + +impl MyApp {} + +impl Default for MyApp { + fn default() -> Self { + Self { + name: "Arthur".to_owned(), + age: 42, + keypad: Keypad::new(), + } + } +} + +impl eframe::App for MyApp { + fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { + egui::Window::new("Custom Keypad") + .default_pos([100.0, 100.0]) + .title_bar(true) + .show(ctx, |ui| { + ui.horizontal(|ui| { + ui.label("Your name: "); + ui.text_edit_singleline(&mut self.name); + }); + ui.add(egui::Slider::new(&mut self.age, 0..=120).text("age")); + if ui.button("Increment").clicked() { + self.age += 1; + } + ui.label(format!("Hello '{}', age {}", self.name, self.age)); + }); + + self.keypad.show(ctx); + } + + fn raw_input_hook(&mut self, ctx: &egui::Context, raw_input: &mut egui::RawInput) { + self.keypad.bump_events(ctx, raw_input); + } +} From d0a6bbf2b880ae00dd3db1185591ba9c0ed51ed6 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 12 Mar 2024 11:14:12 +0100 Subject: [PATCH 002/134] Fix style of disabled widgets (#4163) * Broke in https://github.com/emilk/egui/pull/4026/files The `Response::sense` for `enabled: false` widgets was wrong, leading to the wrong widget style. --- crates/egui/src/context.rs | 17 ++++++++--------- crates/egui/src/hit_test.rs | 10 ++++++++++ crates/egui/src/interaction.rs | 26 ++++++++++++++------------ crates/egui/src/response.rs | 7 +++++++ crates/egui/src/widget_rect.rs | 7 +++++++ 5 files changed, 46 insertions(+), 21 deletions(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 8e09414a6be..232cca3859e 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -1018,12 +1018,7 @@ impl Context { /// /// If the widget already exists, its state (sense, Rect, etc) will be updated. #[allow(clippy::too_many_arguments)] - pub(crate) fn create_widget(&self, mut w: WidgetRect) -> Response { - if !w.enabled { - w.sense.click = false; - w.sense.drag = false; - } - + pub(crate) fn create_widget(&self, w: WidgetRect) -> Response { // Remember this widget self.write(|ctx| { let viewport = ctx.viewport(); @@ -1130,7 +1125,8 @@ impl Context { let input = &viewport.input; let memory = &mut ctx.memory; - if sense.click + if enabled + && sense.click && memory.has_focus(id) && (input.key_pressed(Key::Space) || input.key_pressed(Key::Enter)) { @@ -1139,7 +1135,10 @@ impl Context { } #[cfg(feature = "accesskit")] - if sense.click && input.has_accesskit_action_request(id, accesskit::Action::Default) { + if enabled + && sense.click + && input.has_accesskit_action_request(id, accesskit::Action::Default) + { res.clicked[PointerButton::Primary as usize] = true; } @@ -1159,7 +1158,7 @@ impl Context { for pointer_event in &input.pointer.pointer_events { if let PointerEvent::Released { click, button } = pointer_event { - if sense.click && clicked { + if enabled && sense.click && clicked { if let Some(click) = click { res.clicked[*button as usize] = true; res.double_clicked[*button as usize] = click.is_double(); diff --git a/crates/egui/src/hit_test.rs b/crates/egui/src/hit_test.rs index 2b778e9d719..62a762aa52d 100644 --- a/crates/egui/src/hit_test.rs +++ b/crates/egui/src/hit_test.rs @@ -78,6 +78,16 @@ pub fn hit_test( let top_layer = closest_hit.layer_id; close.retain(|w| w.layer_id == top_layer); + // If the widget is disabled, treat it as if it isn't sensing anything. + // This simplifies the code in `hit_test_on_close` so it doesn't have to check + // the `enabled` flag everywhere: + for w in &mut close { + if !w.enabled { + w.sense.click = false; + w.sense.drag = false; + } + } + let pos_in_layer = pos_in_layers.get(&top_layer).copied().unwrap_or(pos); let hits = hit_test_on_close(&close, pos_in_layer); diff --git a/crates/egui/src/interaction.rs b/crates/egui/src/interaction.rs index 2f1925c7528..20d54887018 100644 --- a/crates/egui/src/interaction.rs +++ b/crates/egui/src/interaction.rs @@ -161,18 +161,20 @@ pub(crate) fn interact( if dragged.is_none() { // Check if we started dragging something new: if let Some(widget) = interaction.potential_drag_id.and_then(|id| widgets.get(id)) { - let is_dragged = if widget.sense.click && widget.sense.drag { - // This widget is sensitive to both clicks and drags. - // When the mouse first is pressed, it could be either, - // so we postpone the decision until we know. - input.pointer.is_decidedly_dragging() - } else { - // This widget is just sensitive to drags, so we can mark it as dragged right away: - widget.sense.drag - }; - - if is_dragged { - dragged = Some(widget.id); + if widget.enabled { + let is_dragged = if widget.sense.click && widget.sense.drag { + // This widget is sensitive to both clicks and drags. + // When the mouse first is pressed, it could be either, + // so we postpone the decision until we know. + input.pointer.is_decidedly_dragging() + } else { + // This widget is just sensitive to drags, so we can mark it as dragged right away: + widget.sense.drag + }; + + if is_dragged { + dragged = Some(widget.id); + } } } } diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index a34ed8fa782..ddd683c4c8f 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -42,6 +42,13 @@ pub struct Response { pub interact_rect: Rect, /// The senses (click and/or drag) that the widget was interested in (if any). + /// + /// Note: if [`Self::enabled`] is `false`, then + /// the widget _effectively_ doesn't sense anything, + /// but can still have the same `Sense`. + /// This is because the sense informs the styling of the widget, + /// but we don't want to change the style when a widget is disabled + /// (that is handled by the `Painter` directly). pub sense: Sense, /// Was the widget enabled? diff --git a/crates/egui/src/widget_rect.rs b/crates/egui/src/widget_rect.rs index acf3dd95df5..ab95447e1fd 100644 --- a/crates/egui/src/widget_rect.rs +++ b/crates/egui/src/widget_rect.rs @@ -29,6 +29,13 @@ pub struct WidgetRect { pub interact_rect: Rect, /// How the widget responds to interaction. + /// + /// Note: if [`Self::enabled`] is `false`, then + /// the widget _effectively_ doesn't sense anything, + /// but can still have the same `Sense`. + /// This is because the sense informs the styling of the widget, + /// but we don't want to change the style when a widget is disabled + /// (that is handled by the `Painter` directly). pub sense: Sense, /// Is the widget enabled? From 4a330de53d2a79a65e3920f80701c7b8634a3ced Mon Sep 17 00:00:00 2001 From: CrazyCraftix <43807375+CrazyCraftix@users.noreply.github.com> Date: Tue, 12 Mar 2024 11:14:24 +0100 Subject: [PATCH 003/134] Fix scaling of rounded corners for rect shape (#4152) (#4161) When scaling an `egui::Shape` of variant `Rect` using the new `transform` function, corner rounding isn't taken into account. The fix is to multiply the rounding by the scaling factor. * Closes --- crates/epaint/src/shape.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/epaint/src/shape.rs b/crates/epaint/src/shape.rs index 0aeec5525a5..fefae6c93a1 100644 --- a/crates/epaint/src/shape.rs +++ b/crates/epaint/src/shape.rs @@ -403,6 +403,7 @@ impl Shape { Self::Rect(rect_shape) => { rect_shape.rect = transform * rect_shape.rect; rect_shape.stroke.width *= transform.scaling; + rect_shape.rounding *= transform.scaling; } Self::Text(text_shape) => { text_shape.pos = transform * text_shape.pos; From efc0a6385c7a70f802697e4a29f2dfdc5367a61f Mon Sep 17 00:00:00 2001 From: ming08108 Date: Tue, 12 Mar 2024 05:14:40 -0500 Subject: [PATCH 004/134] Update kb modifiers from web mouse events (#4156) Update modifier state from web mouse events. This allows modifiers to be correctly updated when the window is not in focus but the mouse is still moving over the window. --- CONTRIBUTING.md | 4 ++-- crates/eframe/src/web/events.rs | 12 +++++++++--- crates/eframe/src/web/input.rs | 18 +++++++++++++++++- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b33189280e7..206829e7aa8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -44,7 +44,7 @@ Please keep pull requests small and focused. The smaller it is, the more likely Most PR reviews are done by me, Emil, but I very much appreciate any help I can get reviewing PRs! -It is very easy to add complexity to a project, but remember that each line of code added is code that needs to be maintained in perpituity, so we have a high bar on what get merged! +It is very easy to add complexity to a project, but remember that each line of code added is code that needs to be maintained in perpetuity, so we have a high bar on what get merged! When reviewing, we look for: * The PR title and description should be helpful @@ -123,7 +123,7 @@ with `Vec2::X` increasing to the right and `Vec2::Y` increasing downwards. `egui` uses logical _points_ as its coordinate system. Those related to physical _pixels_ by the `pixels_per_point` scale factor. -For example, a high-dpi screeen can have `pixels_per_point = 2.0`, +For example, a high-dpi screen can have `pixels_per_point = 2.0`, meaning there are two physical screen pixels for each logical point. Angles are in radians, and are measured clockwise from the X-axis, which has angle=0. diff --git a/crates/eframe/src/web/events.rs b/crates/eframe/src/web/events.rs index 13bea256098..71fe04e0c14 100644 --- a/crates/eframe/src/web/events.rs +++ b/crates/eframe/src/web/events.rs @@ -87,7 +87,7 @@ pub(crate) fn install_document_events(runner_ref: &WebRunner) -> Result<(), JsVa return; } - let modifiers = modifiers_from_event(&event); + let modifiers = modifiers_from_kb_event(&event); runner.input.raw.modifiers = modifiers; let key = event.key(); @@ -158,7 +158,7 @@ pub(crate) fn install_document_events(runner_ref: &WebRunner) -> Result<(), JsVa &document, "keyup", |event: web_sys::KeyboardEvent, runner| { - let modifiers = modifiers_from_event(&event); + let modifiers = modifiers_from_kb_event(&event); runner.input.raw.modifiers = modifiers; if let Some(key) = translate_key(&event.key()) { runner.input.raw.events.push(egui::Event::Key { @@ -301,6 +301,8 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu &canvas, "mousedown", |event: web_sys::MouseEvent, runner: &mut AppRunner| { + let modifiers = modifiers_from_mouse_event(&event); + runner.input.raw.modifiers = modifiers; if let Some(button) = button_from_mouse_event(&event) { let pos = pos_from_mouse_event(runner.canvas_id(), &event); let modifiers = runner.input.raw.modifiers; @@ -327,6 +329,8 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu &canvas, "mousemove", |event: web_sys::MouseEvent, runner| { + let modifiers = modifiers_from_mouse_event(&event); + runner.input.raw.modifiers = modifiers; let pos = pos_from_mouse_event(runner.canvas_id(), &event); runner.input.raw.events.push(egui::Event::PointerMoved(pos)); runner.needs_repaint.repaint_asap(); @@ -336,6 +340,8 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu )?; runner_ref.add_event_listener(&canvas, "mouseup", |event: web_sys::MouseEvent, runner| { + let modifiers = modifiers_from_mouse_event(&event); + runner.input.raw.modifiers = modifiers; if let Some(button) = button_from_mouse_event(&event) { let pos = pos_from_mouse_event(runner.canvas_id(), &event); let modifiers = runner.input.raw.modifiers; @@ -474,7 +480,7 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu // Report a zoom event in case CTRL (on Windows or Linux) or CMD (on Mac) is pressed. // This if-statement is equivalent to how `Modifiers.command` is determined in - // `modifiers_from_event()`, but we cannot directly use that fn for a [`WheelEvent`]. + // `modifiers_from_kb_event()`, but we cannot directly use that fn for a [`WheelEvent`]. if event.ctrl_key() || event.meta_key() { let factor = (delta.y / 200.0).exp(); runner.input.raw.events.push(egui::Event::Zoom(factor)); diff --git a/crates/eframe/src/web/input.rs b/crates/eframe/src/web/input.rs index 96cad32e20f..223693f4263 100644 --- a/crates/eframe/src/web/input.rs +++ b/crates/eframe/src/web/input.rs @@ -115,7 +115,23 @@ pub fn translate_key(key: &str) -> Option { egui::Key::from_name(key) } -pub fn modifiers_from_event(event: &web_sys::KeyboardEvent) -> egui::Modifiers { +pub fn modifiers_from_kb_event(event: &web_sys::KeyboardEvent) -> egui::Modifiers { + egui::Modifiers { + alt: event.alt_key(), + ctrl: event.ctrl_key(), + shift: event.shift_key(), + + // Ideally we should know if we are running or mac or not, + // but this works good enough for now. + mac_cmd: event.meta_key(), + + // Ideally we should know if we are running or mac or not, + // but this works good enough for now. + command: event.ctrl_key() || event.meta_key(), + } +} + +pub fn modifiers_from_mouse_event(event: &web_sys::MouseEvent) -> egui::Modifiers { egui::Modifiers { alt: event.alt_key(), ctrl: event.ctrl_key(), From c87bcc4bccd76938f3f7aff9a1f10903d032bf2e Mon Sep 17 00:00:00 2001 From: Vincent Alsteen Date: Tue, 12 Mar 2024 11:15:13 +0100 Subject: [PATCH 005/134] Remove unnecessary allocation in `RepaintCause::new` (#4146) Hi! I'm using egui for the UI of a VST3/Clap plugin, and this kind of environment is rather picky on performance. I use [assert_no_alloc](https://crates.io/crates/assert_no_alloc) to make sure the audio thread is never allocating. The audio thread may request a repaint of the GUI tho, and this is where a saw that it may allocate when tracing the repaint reason. Turns out it's not necessary, `Location::caller` is `'static`, so using a `&'static str` instead of a `String` in `RepaintCause::file` will just work, so this PR just does that. --- crates/egui/src/context.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 232cca3859e..c1dcd5b5be9 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -256,7 +256,7 @@ struct ViewportState { #[derive(Clone, Debug)] pub struct RepaintCause { /// What file had the call that requested the repaint? - pub file: String, + pub file: &'static str, /// What line number of the the call that requested the repaint? pub line: u32, @@ -269,7 +269,7 @@ impl RepaintCause { pub fn new() -> Self { let caller = Location::caller(); Self { - file: caller.file().to_owned(), + file: caller.file(), line: caller.line(), } } From f0190320333c8a8f5f5520833d754ada1ce0f529 Mon Sep 17 00:00:00 2001 From: rustbasic <127506429+rustbasic@users.noreply.github.com> Date: Tue, 12 Mar 2024 19:21:33 +0900 Subject: [PATCH 006/134] TextEdit: fix crash when hitting SHIFT + TAB around non-ASCII text (#3984) * Closes #3846 * Closes #3878 Dear emilk. Leaving aside other function, I think this is all you need to fix to patch the panic that occurs when Shift + TAB. Thank you, emilk. --- crates/egui/src/widgets/text_edit/text_buffer.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/egui/src/widgets/text_edit/text_buffer.rs b/crates/egui/src/widgets/text_edit/text_buffer.rs index f28878a1dd3..e70ce695af9 100644 --- a/crates/egui/src/widgets/text_edit/text_buffer.rs +++ b/crates/egui/src/widgets/text_edit/text_buffer.rs @@ -89,10 +89,12 @@ pub trait TextBuffer { fn decrease_indentation(&mut self, ccursor: &mut CCursor) { let line_start = find_line_start(self.as_str(), *ccursor); - let remove_len = if self.as_str()[line_start.index..].starts_with('\t') { + let remove_len = if self.as_str().chars().nth(line_start.index) == Some('\t') { Some(1) - } else if self.as_str()[line_start.index..] + } else if self + .as_str() .chars() + .skip(line_start.index) .take(TAB_SIZE) .all(|c| c == ' ') { From 0afbefc884cc6d8ada9c60461b1b7c89415270b1 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 13 Mar 2024 12:32:54 +0100 Subject: [PATCH 007/134] Improve logic for when submenus are kept open (#4166) * Closes https://github.com/emilk/egui/issues/2853 * Closes https://github.com/emilk/egui/issues/4101 * Reverts parts of https://github.com/emilk/egui/pull/3055 --- crates/egui/src/menu.rs | 34 +++++++++++++++++++++++++--------- crates/egui/src/ui.rs | 7 ++++++- crates/emath/src/rect.rs | 26 ++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 10 deletions(-) diff --git a/crates/egui/src/menu.rs b/crates/egui/src/menu.rs index 67689ca9d4e..1f7a74fbf4d 100644 --- a/crates/egui/src/menu.rs +++ b/crates/egui/src/menu.rs @@ -613,30 +613,42 @@ impl MenuState { let pointer = ui.input(|i| i.pointer.clone()); let open = self.is_open(sub_id); if self.moving_towards_current_submenu(&pointer) { + // We don't close the submenu if the pointer is on its way to hover it. // ensure to repaint once even when pointer is not moving ui.ctx().request_repaint(); } else if !open && button.hovered() { let pos = button.rect.right_top(); self.open_submenu(sub_id, pos); + } else if open + && ui.interact_bg(Sense::hover()).contains_pointer() + && !button.hovered() + && !self.hovering_current_submenu(&pointer) + { + // We are hovering something else in the menu, so close the submenu. + self.close_submenu(); } } - /// Check if `dir` points from `pos` towards left side of `rect`. - fn points_at_left_of_rect(pos: Pos2, dir: Vec2, rect: Rect) -> bool { - let vel_a = dir.angle(); - let top_a = (rect.left_top() - pos).angle(); - let bottom_a = (rect.left_bottom() - pos).angle(); - bottom_a - vel_a >= 0.0 && top_a - vel_a <= 0.0 - } - /// Check if pointer is moving towards current submenu. fn moving_towards_current_submenu(&self, pointer: &PointerState) -> bool { if pointer.is_still() { return false; } + if let Some(sub_menu) = self.current_submenu() { if let Some(pos) = pointer.hover_pos() { - return Self::points_at_left_of_rect(pos, pointer.velocity(), sub_menu.read().rect); + let rect = sub_menu.read().rect; + return rect.intesects_ray(pos, pointer.velocity().normalized()); + } + } + false + } + + /// Check if pointer is hovering current submenu. + fn hovering_current_submenu(&self, pointer: &PointerState) -> bool { + if let Some(sub_menu) = self.current_submenu() { + if let Some(pos) = pointer.hover_pos() { + return sub_menu.read().area_contains(pos); } } false @@ -673,4 +685,8 @@ impl MenuState { self.sub_menu = Some((id, Arc::new(RwLock::new(Self::new(pos))))); } } + + fn close_submenu(&mut self) { + self.sub_menu = None; + } } diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 4e28ece40f3..56db9e97f60 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -714,8 +714,13 @@ impl Ui { .rect_contains_pointer(self.layer_id(), self.clip_rect().intersect(rect)) } - /// Is the pointer (mouse/touch) above this [`Ui`]? + /// Is the pointer (mouse/touch) above the current [`Ui`]? + /// /// Equivalent to `ui.rect_contains_pointer(ui.min_rect())` + /// + /// Note that this tests against the _current_ [`Ui::min_rect`]. + /// If you want to test against the final `min_rect`, + /// use [`Self::interact_bg`] instead. pub fn ui_contains_pointer(&self) -> bool { self.rect_contains_pointer(self.min_rect()) } diff --git a/crates/emath/src/rect.rs b/crates/emath/src/rect.rs index 4e6ee362c25..812d76c0d14 100644 --- a/crates/emath/src/rect.rs +++ b/crates/emath/src/rect.rs @@ -605,6 +605,32 @@ impl Rect { } } +impl Rect { + /// Does this Rect intersect the given ray (where `d` is normalized)? + pub fn intesects_ray(&self, o: Pos2, d: Vec2) -> bool { + let mut tmin = -f32::INFINITY; + let mut tmax = f32::INFINITY; + + if d.x != 0.0 { + let tx1 = (self.min.x - o.x) / d.x; + let tx2 = (self.max.x - o.x) / d.x; + + tmin = tmin.max(tx1.min(tx2)); + tmax = tmax.min(tx1.max(tx2)); + } + + if d.y != 0.0 { + let ty1 = (self.min.y - o.y) / d.y; + let ty2 = (self.max.y - o.y) / d.y; + + tmin = tmin.max(ty1.min(ty2)); + tmax = tmax.min(ty1.max(ty2)); + } + + tmin <= tmax + } +} + impl std::fmt::Debug for Rect { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "[{:?} - {:?}]", self.min, self.max) From 47fbce665ab64bd2595e960f674427d94b28ec94 Mon Sep 17 00:00:00 2001 From: lomekragow <51994883+Chaojimengnan@users.noreply.github.com> Date: Wed, 13 Mar 2024 20:07:27 +0800 Subject: [PATCH 008/134] Add `get_proc_address` in CreationContext (#4145) * Closes --- crates/eframe/src/epi.rs | 4 ++++ crates/eframe/src/native/glow_integration.rs | 2 ++ crates/eframe/src/native/wgpu_integration.rs | 2 ++ crates/eframe/src/web/app_runner.rs | 3 +++ 4 files changed, 11 insertions(+) diff --git a/crates/eframe/src/epi.rs b/crates/eframe/src/epi.rs index 16a8a689102..72ad27b80b7 100644 --- a/crates/eframe/src/epi.rs +++ b/crates/eframe/src/epi.rs @@ -67,6 +67,10 @@ pub struct CreationContext<'s> { #[cfg(feature = "glow")] pub gl: Option>, + /// The `get_proc_address` wrapper of underlying GL context + #[cfg(feature = "glow")] + pub get_proc_address: Option<&'s dyn Fn(&std::ffi::CStr) -> *const std::ffi::c_void>, + /// The underlying WGPU render state. /// /// Only available when compiling with the `wgpu` feature and using [`Renderer::Wgpu`]. diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index 0cb7ec331f9..8b1f16ec807 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -284,12 +284,14 @@ impl GlowWinitApp { // Use latest raw_window_handle for eframe compatibility use raw_window_handle::{HasDisplayHandle as _, HasWindowHandle as _}; + let get_proc_address = |addr: &_| glutin.get_proc_address(addr); let window = glutin.window(ViewportId::ROOT); let cc = CreationContext { egui_ctx: integration.egui_ctx.clone(), integration_info: integration.frame.info().clone(), storage: integration.frame.storage(), gl: Some(gl), + get_proc_address: Some(&get_proc_address), #[cfg(feature = "wgpu")] wgpu_render_state: None, raw_display_handle: window.display_handle().map(|h| h.as_raw()), diff --git a/crates/eframe/src/native/wgpu_integration.rs b/crates/eframe/src/native/wgpu_integration.rs index b3451be9cb0..61bf157c007 100644 --- a/crates/eframe/src/native/wgpu_integration.rs +++ b/crates/eframe/src/native/wgpu_integration.rs @@ -262,6 +262,8 @@ impl WgpuWinitApp { storage: integration.frame.storage(), #[cfg(feature = "glow")] gl: None, + #[cfg(feature = "glow")] + get_proc_address: None, wgpu_render_state, raw_display_handle: window.display_handle().map(|h| h.as_raw()), raw_window_handle: window.window_handle().map(|h| h.as_raw()), diff --git a/crates/eframe/src/web/app_runner.rs b/crates/eframe/src/web/app_runner.rs index 6cb331dfba9..d63469110c7 100644 --- a/crates/eframe/src/web/app_runner.rs +++ b/crates/eframe/src/web/app_runner.rs @@ -76,6 +76,9 @@ impl AppRunner { #[cfg(feature = "glow")] gl: Some(painter.gl().clone()), + #[cfg(feature = "glow")] + get_proc_address: None, + #[cfg(all(feature = "wgpu", not(feature = "glow")))] wgpu_render_state: painter.render_state(), #[cfg(all(feature = "wgpu", feature = "glow"))] From 00e8ce6d7eed351ffa4a151b9d24ce7549ab22ec Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 14 Mar 2024 10:18:07 +0100 Subject: [PATCH 009/134] Reduce log-level of processing ViewportCommands --- crates/egui-winit/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index ac29a0b2404..c02fd8d275a 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -1287,7 +1287,7 @@ fn process_viewport_command( use winit::window::ResizeDirection; - log::debug!("Processing ViewportCommand::{command:?}"); + log::trace!("Processing ViewportCommand::{command:?}"); let pixels_per_point = pixels_per_point(egui_ctx, window); From c5eaba43cd6713eb12ddf92d83a75af471bb6b55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Proch=C3=A1zka?= Date: Thu, 14 Mar 2024 10:26:34 +0100 Subject: [PATCH 010/134] Fix crash on `request_animation_frame` when destroying web runner (#4169) Previously, any frames in flight (`requestAnimationFrame`) on web were not being cancelled (`cancelAnimationFrame`) when `WebRunner::destroy` was called. If a user called `destroy`, then immediately removed the canvas from the DOM, eframe could panic with a "failed to find (canvas) element by id" error message. This PR changes two things: - The canvas element is directly referenced everywhere it's needed instead of being looked up by `canvas_id`[^1] - The RAF handle is stored in `WebRunner` and `cancelAnimationFrame` is called on it inside of `WebRunner::destroy`[^2] [^1]: The WebGL/WGPU backends were already holding onto the canvas (and associated GPU context), so the change is just converting all the `get_element_by_id` lookups to retrieve the canvas from the web runner handle. [^2]: There is only ever one frame in flight, so we store it directly as a scalar field. --- crates/eframe/src/web/app_runner.rs | 10 ++++---- crates/eframe/src/web/events.rs | 26 ++++++++------------- crates/eframe/src/web/input.rs | 14 +++++++----- crates/eframe/src/web/mod.rs | 21 ++++++++--------- crates/eframe/src/web/text_agent.rs | 10 ++++---- crates/eframe/src/web/web_painter.rs | 4 ++-- crates/eframe/src/web/web_painter_glow.rs | 13 ++++------- crates/eframe/src/web/web_painter_wgpu.rs | 8 +++---- crates/eframe/src/web/web_runner.rs | 28 +++++++++++++++++++++-- 9 files changed, 73 insertions(+), 61 deletions(-) diff --git a/crates/eframe/src/web/app_runner.rs b/crates/eframe/src/web/app_runner.rs index d63469110c7..31f6e79c01c 100644 --- a/crates/eframe/src/web/app_runner.rs +++ b/crates/eframe/src/web/app_runner.rs @@ -165,8 +165,8 @@ impl AppRunner { self.last_save_time = now_sec(); } - pub fn canvas_id(&self) -> &str { - self.painter.canvas_id() + pub fn canvas(&self) -> &web_sys::HtmlCanvasElement { + self.painter.canvas() } pub fn destroy(mut self) { @@ -182,8 +182,8 @@ impl AppRunner { /// /// The result can be painted later with a call to [`Self::run_and_paint`] or [`Self::paint`]. pub fn logic(&mut self) { - super::resize_canvas_to_screen_size(self.canvas_id(), self.web_options.max_size_points); - let canvas_size = super::canvas_size_in_points(self.canvas_id()); + super::resize_canvas_to_screen_size(self.canvas(), self.web_options.max_size_points); + let canvas_size = super::canvas_size_in_points(self.canvas()); let raw_input = self.input.new_frame(canvas_size); let full_output = self.egui_ctx.run(raw_input, |egui_ctx| { @@ -268,7 +268,7 @@ impl AppRunner { self.mutable_text_under_cursor = mutable_text_under_cursor; if self.ime != ime { - super::text_agent::move_text_cursor(ime, self.canvas_id()); + super::text_agent::move_text_cursor(ime, self.canvas()); self.ime = ime; } } diff --git a/crates/eframe/src/web/events.rs b/crates/eframe/src/web/events.rs index 71fe04e0c14..5e5f7b22622 100644 --- a/crates/eframe/src/web/events.rs +++ b/crates/eframe/src/web/events.rs @@ -5,12 +5,12 @@ use super::*; /// Calls `request_animation_frame` to schedule repaint. /// /// It will only paint if needed, but will always call `request_animation_frame` immediately. -fn paint_and_schedule(runner_ref: &WebRunner) -> Result<(), JsValue> { +pub(crate) fn paint_and_schedule(runner_ref: &WebRunner) -> Result<(), JsValue> { // Only paint and schedule if there has been no panic if let Some(mut runner_lock) = runner_ref.try_lock() { paint_if_needed(&mut runner_lock); drop(runner_lock); - request_animation_frame(runner_ref.clone())?; + runner_ref.request_animation_frame()?; } Ok(()) } @@ -45,14 +45,6 @@ fn paint_if_needed(runner: &mut AppRunner) { runner.auto_save_if_needed(); } -pub(crate) fn request_animation_frame(runner_ref: WebRunner) -> Result<(), JsValue> { - let window = web_sys::window().unwrap(); - let closure = Closure::once(move || paint_and_schedule(&runner_ref)); - window.request_animation_frame(closure.as_ref().unchecked_ref())?; - closure.forget(); // We must forget it, or else the callback is canceled on drop - Ok(()) -} - // ------------------------------------------------------------------------ pub(crate) fn install_document_events(runner_ref: &WebRunner) -> Result<(), JsValue> { @@ -275,7 +267,7 @@ pub(crate) fn install_color_scheme_change_event(runner_ref: &WebRunner) -> Resul } pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValue> { - let canvas = canvas_element(runner_ref.try_lock().unwrap().canvas_id()).unwrap(); + let canvas = runner_ref.try_lock().unwrap().canvas().clone(); { let prevent_default_events = [ @@ -304,7 +296,7 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu let modifiers = modifiers_from_mouse_event(&event); runner.input.raw.modifiers = modifiers; if let Some(button) = button_from_mouse_event(&event) { - let pos = pos_from_mouse_event(runner.canvas_id(), &event); + let pos = pos_from_mouse_event(runner.canvas(), &event); let modifiers = runner.input.raw.modifiers; runner.input.raw.events.push(egui::Event::PointerButton { pos, @@ -331,7 +323,7 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu |event: web_sys::MouseEvent, runner| { let modifiers = modifiers_from_mouse_event(&event); runner.input.raw.modifiers = modifiers; - let pos = pos_from_mouse_event(runner.canvas_id(), &event); + let pos = pos_from_mouse_event(runner.canvas(), &event); runner.input.raw.events.push(egui::Event::PointerMoved(pos)); runner.needs_repaint.repaint_asap(); event.stop_propagation(); @@ -343,7 +335,7 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu let modifiers = modifiers_from_mouse_event(&event); runner.input.raw.modifiers = modifiers; if let Some(button) = button_from_mouse_event(&event) { - let pos = pos_from_mouse_event(runner.canvas_id(), &event); + let pos = pos_from_mouse_event(runner.canvas(), &event); let modifiers = runner.input.raw.modifiers; runner.input.raw.events.push(egui::Event::PointerButton { pos, @@ -381,7 +373,7 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu "touchstart", |event: web_sys::TouchEvent, runner| { let mut latest_touch_pos_id = runner.input.latest_touch_pos_id; - let pos = pos_from_touch_event(runner.canvas_id(), &event, &mut latest_touch_pos_id); + let pos = pos_from_touch_event(runner.canvas(), &event, &mut latest_touch_pos_id); runner.input.latest_touch_pos_id = latest_touch_pos_id; runner.input.latest_touch_pos = Some(pos); let modifiers = runner.input.raw.modifiers; @@ -404,7 +396,7 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu "touchmove", |event: web_sys::TouchEvent, runner| { let mut latest_touch_pos_id = runner.input.latest_touch_pos_id; - let pos = pos_from_touch_event(runner.canvas_id(), &event, &mut latest_touch_pos_id); + let pos = pos_from_touch_event(runner.canvas(), &event, &mut latest_touch_pos_id); runner.input.latest_touch_pos_id = latest_touch_pos_id; runner.input.latest_touch_pos = Some(pos); runner.input.raw.events.push(egui::Event::PointerMoved(pos)); @@ -467,7 +459,7 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu }); let scroll_multiplier = match unit { - egui::MouseWheelUnit::Page => canvas_size_in_points(runner.canvas_id()).y, + egui::MouseWheelUnit::Page => canvas_size_in_points(runner.canvas()).y, egui::MouseWheelUnit::Line => { #[allow(clippy::let_and_return)] let points_per_scroll_line = 8.0; // Note that this is intentionally different from what we use in winit. diff --git a/crates/eframe/src/web/input.rs b/crates/eframe/src/web/input.rs index 223693f4263..4ed0227739a 100644 --- a/crates/eframe/src/web/input.rs +++ b/crates/eframe/src/web/input.rs @@ -1,7 +1,9 @@ -use super::{canvas_element, canvas_origin, AppRunner}; +use super::{canvas_origin, AppRunner}; -pub fn pos_from_mouse_event(canvas_id: &str, event: &web_sys::MouseEvent) -> egui::Pos2 { - let canvas = canvas_element(canvas_id).unwrap(); +pub fn pos_from_mouse_event( + canvas: &web_sys::HtmlCanvasElement, + event: &web_sys::MouseEvent, +) -> egui::Pos2 { let rect = canvas.get_bounding_client_rect(); egui::Pos2 { x: event.client_x() as f32 - rect.left() as f32, @@ -27,7 +29,7 @@ pub fn button_from_mouse_event(event: &web_sys::MouseEvent) -> Option, ) -> egui::Pos2 { @@ -47,7 +49,7 @@ pub fn pos_from_touch_event( .or_else(|| event.touches().get(0)) .map_or(Default::default(), |touch| { *touch_id_for_pos = Some(egui::TouchId::from(touch.identifier())); - pos_from_touch(canvas_origin(canvas_id), &touch) + pos_from_touch(canvas_origin(canvas), &touch) }) } @@ -59,7 +61,7 @@ fn pos_from_touch(canvas_origin: egui::Pos2, touch: &web_sys::Touch) -> egui::Po } pub fn push_touches(runner: &mut AppRunner, phase: egui::TouchPhase, event: &web_sys::TouchEvent) { - let canvas_origin = canvas_origin(runner.canvas_id()); + let canvas_origin = canvas_origin(runner.canvas()); for touch_idx in 0..event.changed_touches().length() { if let Some(touch) = event.changed_touches().item(touch_idx) { runner.input.raw.events.push(egui::Event::Touch { diff --git a/crates/eframe/src/web/mod.rs b/crates/eframe/src/web/mod.rs index cb5d6937c4c..d88e94229fd 100644 --- a/crates/eframe/src/web/mod.rs +++ b/crates/eframe/src/web/mod.rs @@ -100,26 +100,23 @@ fn theme_from_dark_mode(dark_mode: bool) -> Theme { } } -fn canvas_element(canvas_id: &str) -> Option { +fn get_canvas_element_by_id(canvas_id: &str) -> Option { let document = web_sys::window()?.document()?; let canvas = document.get_element_by_id(canvas_id)?; canvas.dyn_into::().ok() } -fn canvas_element_or_die(canvas_id: &str) -> web_sys::HtmlCanvasElement { - canvas_element(canvas_id) +fn get_canvas_element_by_id_or_die(canvas_id: &str) -> web_sys::HtmlCanvasElement { + get_canvas_element_by_id(canvas_id) .unwrap_or_else(|| panic!("Failed to find canvas with id {canvas_id:?}")) } -fn canvas_origin(canvas_id: &str) -> egui::Pos2 { - let rect = canvas_element(canvas_id) - .unwrap() - .get_bounding_client_rect(); +fn canvas_origin(canvas: &web_sys::HtmlCanvasElement) -> egui::Pos2 { + let rect = canvas.get_bounding_client_rect(); egui::pos2(rect.left() as f32, rect.top() as f32) } -fn canvas_size_in_points(canvas_id: &str) -> egui::Vec2 { - let canvas = canvas_element(canvas_id).unwrap(); +fn canvas_size_in_points(canvas: &web_sys::HtmlCanvasElement) -> egui::Vec2 { let pixels_per_point = native_pixels_per_point(); egui::vec2( canvas.width() as f32 / pixels_per_point, @@ -127,8 +124,10 @@ fn canvas_size_in_points(canvas_id: &str) -> egui::Vec2 { ) } -fn resize_canvas_to_screen_size(canvas_id: &str, max_size_points: egui::Vec2) -> Option<()> { - let canvas = canvas_element(canvas_id)?; +fn resize_canvas_to_screen_size( + canvas: &web_sys::HtmlCanvasElement, + max_size_points: egui::Vec2, +) -> Option<()> { let parent = canvas.parent_element()?; // Prefer the client width and height so that if the parent diff --git a/crates/eframe/src/web/text_agent.rs b/crates/eframe/src/web/text_agent.rs index 0bf8b532b7c..a879f99d008 100644 --- a/crates/eframe/src/web/text_agent.rs +++ b/crates/eframe/src/web/text_agent.rs @@ -5,7 +5,7 @@ use std::{cell::Cell, rc::Rc}; use wasm_bindgen::prelude::*; -use super::{canvas_element, AppRunner, WebRunner}; +use super::{AppRunner, WebRunner}; static AGENT_ID: &str = "egui_text_agent"; @@ -121,7 +121,7 @@ pub fn update_text_agent(runner: &mut AppRunner) -> Option<()> { let window = web_sys::window()?; let document = window.document()?; let input: HtmlInputElement = document.get_element_by_id(AGENT_ID)?.dyn_into().unwrap(); - let canvas_style = canvas_element(runner.canvas_id())?.style(); + let canvas_style = runner.canvas().style(); if runner.mutable_text_under_cursor { let is_already_editing = input.hidden(); @@ -205,14 +205,16 @@ fn is_mobile() -> Option { // candidate window moves following text element (agent), // so it appears that the IME candidate window moves with text cursor. // On mobile devices, there is no need to do that. -pub fn move_text_cursor(ime: Option, canvas_id: &str) -> Option<()> { +pub fn move_text_cursor( + ime: Option, + canvas: &web_sys::HtmlCanvasElement, +) -> Option<()> { let style = text_agent().style(); // Note: moving agent on mobile devices will lead to unpredictable scroll. if is_mobile() == Some(false) { ime.as_ref().and_then(|ime| { let egui::Pos2 { x, y } = ime.cursor_rect.left_top(); - let canvas = canvas_element(canvas_id)?; let bounding_rect = text_agent().get_bounding_client_rect(); let y = (y + (canvas.scroll_top() + canvas.offset_top()) as f32) .min(canvas.client_height() as f32 - bounding_rect.height() as f32); diff --git a/crates/eframe/src/web/web_painter.rs b/crates/eframe/src/web/web_painter.rs index 9c7631b90eb..e4db8eac316 100644 --- a/crates/eframe/src/web/web_painter.rs +++ b/crates/eframe/src/web/web_painter.rs @@ -9,8 +9,8 @@ pub(crate) trait WebPainter { // where // Self: Sized; - /// Id of the canvas in use. - fn canvas_id(&self) -> &str; + /// Reference to the canvas in use. + fn canvas(&self) -> &web_sys::HtmlCanvasElement; /// Maximum size of a texture in one direction. fn max_texture_side(&self) -> usize; diff --git a/crates/eframe/src/web/web_painter_glow.rs b/crates/eframe/src/web/web_painter_glow.rs index cd62758688f..b54f6f64423 100644 --- a/crates/eframe/src/web/web_painter_glow.rs +++ b/crates/eframe/src/web/web_painter_glow.rs @@ -10,7 +10,6 @@ use super::web_painter::WebPainter; pub(crate) struct WebPainterGlow { canvas: HtmlCanvasElement, - canvas_id: String, painter: egui_glow::Painter, } @@ -20,7 +19,7 @@ impl WebPainterGlow { } pub async fn new(canvas_id: &str, options: &WebOptions) -> Result { - let canvas = super::canvas_element_or_die(canvas_id); + let canvas = super::get_canvas_element_by_id_or_die(canvas_id); let (gl, shader_prefix) = init_glow_context_from_canvas(&canvas, options.webgl_context_option)?; @@ -30,11 +29,7 @@ impl WebPainterGlow { let painter = egui_glow::Painter::new(gl, shader_prefix, None) .map_err(|err| format!("Error starting glow painter: {err}"))?; - Ok(Self { - canvas, - canvas_id: canvas_id.to_owned(), - painter, - }) + Ok(Self { canvas, painter }) } } @@ -43,8 +38,8 @@ impl WebPainter for WebPainterGlow { self.painter.max_texture_side() } - fn canvas_id(&self) -> &str { - &self.canvas_id + fn canvas(&self) -> &HtmlCanvasElement { + &self.canvas } fn paint_and_update_textures( diff --git a/crates/eframe/src/web/web_painter_wgpu.rs b/crates/eframe/src/web/web_painter_wgpu.rs index 857a1f5404c..de5ba601111 100644 --- a/crates/eframe/src/web/web_painter_wgpu.rs +++ b/crates/eframe/src/web/web_painter_wgpu.rs @@ -41,7 +41,6 @@ impl HasDisplayHandle for EguiWebWindow { pub(crate) struct WebPainterWgpu { canvas: HtmlCanvasElement, - canvas_id: String, surface: wgpu::Surface<'static>, surface_configuration: wgpu::SurfaceConfiguration, render_state: Option, @@ -163,7 +162,7 @@ impl WebPainterWgpu { } } - let canvas = super::canvas_element_or_die(canvas_id); + let canvas = super::get_canvas_element_by_id_or_die(canvas_id); let surface = instance .create_surface(wgpu::SurfaceTarget::Canvas(canvas.clone())) .map_err(|err| format!("failed to create wgpu surface: {err}"))?; @@ -188,7 +187,6 @@ impl WebPainterWgpu { Ok(Self { canvas, - canvas_id: canvas_id.to_owned(), render_state: Some(render_state), surface, surface_configuration, @@ -200,8 +198,8 @@ impl WebPainterWgpu { } impl WebPainter for WebPainterWgpu { - fn canvas_id(&self) -> &str { - &self.canvas_id + fn canvas(&self) -> &HtmlCanvasElement { + &self.canvas } fn max_texture_side(&self) -> usize { diff --git a/crates/eframe/src/web/web_runner.rs b/crates/eframe/src/web/web_runner.rs index 602c566e9d7..f39fc0dcace 100644 --- a/crates/eframe/src/web/web_runner.rs +++ b/crates/eframe/src/web/web_runner.rs @@ -1,4 +1,7 @@ -use std::{cell::RefCell, rc::Rc}; +use std::{ + cell::{Cell, RefCell}, + rc::Rc, +}; use wasm_bindgen::prelude::*; @@ -24,6 +27,9 @@ pub struct WebRunner { /// They have to be in a separate `Rc` so that we don't need to pass them to /// the panic handler, since they aren't `Send`. events_to_unsubscribe: Rc>>, + + /// Used in `destroy` to cancel a pending frame. + request_animation_frame_id: Cell>, } impl WebRunner { @@ -41,6 +47,7 @@ impl WebRunner { panic_handler, runner: Rc::new(RefCell::new(None)), events_to_unsubscribe: Rc::new(RefCell::new(Default::default())), + request_animation_frame_id: Cell::new(None), } } @@ -71,7 +78,7 @@ impl WebRunner { events::install_color_scheme_change_event(self)?; } - events::request_animation_frame(self.clone())?; + self.request_animation_frame()?; } Ok(()) @@ -108,6 +115,11 @@ impl WebRunner { pub fn destroy(&self) { self.unsubscribe_from_all_events(); + if let Some(id) = self.request_animation_frame_id.get() { + let window = web_sys::window().unwrap(); + window.cancel_animation_frame(id).ok(); + } + if let Some(runner) = self.runner.replace(None) { runner.destroy(); } @@ -179,6 +191,18 @@ impl WebRunner { Ok(()) } + + pub(crate) fn request_animation_frame(&self) -> Result<(), wasm_bindgen::JsValue> { + let window = web_sys::window().unwrap(); + let closure = Closure::once({ + let runner_ref = self.clone(); + move || events::paint_and_schedule(&runner_ref) + }); + let id = window.request_animation_frame(closure.as_ref().unchecked_ref())?; + self.request_animation_frame_id.set(Some(id)); + closure.forget(); // We must forget it, or else the callback is canceled on drop + Ok(()) + } } // ---------------------------------------------------------------------------- From bf7ffb982a6e8c0a40d0871c38bccacef9ee8efb Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 15 Mar 2024 06:43:27 +0100 Subject: [PATCH 011/134] Fix bug parsing url query with escaped & or = (#4172) I have tested this manually: Screenshot 2024-03-14 at 19 38 34 --- crates/eframe/src/epi.rs | 2 +- crates/eframe/src/web/backend.rs | 27 ++++++++++++++++----------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/crates/eframe/src/epi.rs b/crates/eframe/src/epi.rs index 72ad27b80b7..a27a37335cc 100644 --- a/crates/eframe/src/epi.rs +++ b/crates/eframe/src/epi.rs @@ -718,7 +718,7 @@ pub struct WebInfo { #[cfg(target_arch = "wasm32")] #[derive(Clone, Debug)] pub struct Location { - /// The full URL (`location.href`) without the hash. + /// The full URL (`location.href`) without the hash, percent-decoded. /// /// Example: `"http://www.example.com:80/index.html?foo=bar"`. pub url: String, diff --git a/crates/eframe/src/web/backend.rs b/crates/eframe/src/web/backend.rs index 2dc3af4e9ac..1121cb43782 100644 --- a/crates/eframe/src/web/backend.rs +++ b/crates/eframe/src/web/backend.rs @@ -99,28 +99,25 @@ pub fn web_location() -> epi::Location { .search() .unwrap_or_default() .strip_prefix('?') - .map(percent_decode) - .unwrap_or_default(); - - let query_map = parse_query_map(&query) - .iter() - .map(|(k, v)| ((*k).to_owned(), (*v).to_owned())) - .collect(); + .unwrap_or_default() + .to_owned(); epi::Location { + // TODO(emilk): should we really percent-decode the url? 🤷‍♂️ url: percent_decode(&location.href().unwrap_or_default()), protocol: percent_decode(&location.protocol().unwrap_or_default()), host: percent_decode(&location.host().unwrap_or_default()), hostname: percent_decode(&location.hostname().unwrap_or_default()), port: percent_decode(&location.port().unwrap_or_default()), hash, + query_map: parse_query_map(&query), query, - query_map, origin: percent_decode(&location.origin().unwrap_or_default()), } } -fn parse_query_map(query: &str) -> BTreeMap<&str, &str> { +/// query is percent-encoded +fn parse_query_map(query: &str) -> BTreeMap { query .split('&') .filter_map(|pair| { @@ -128,15 +125,16 @@ fn parse_query_map(query: &str) -> BTreeMap<&str, &str> { None } else { Some(if let Some((key, value)) = pair.split_once('=') { - (key, value) + (percent_decode(key), percent_decode(value)) } else { - (pair, "") + (percent_decode(pair), String::new()) }) } }) .collect() } +// TODO(emilk): this test is never acgtually run, because this whole module is wasm32 only 🤦‍♂️ #[test] fn test_parse_query() { assert_eq!(parse_query_map(""), BTreeMap::default()); @@ -157,4 +155,11 @@ fn test_parse_query() { parse_query_map("foo&baz&&"), BTreeMap::from_iter([("foo", ""), ("baz", "")]) ); + assert_eq!( + parse_query_map("badger=data.rrd%3Fparam1%3Dfoo%26param2%3Dbar&mushroom=snake"), + BTreeMap::from_iter([ + ("badger", "data.rrd?param1=foo¶m2=bar"), + ("mushroom", "snake") + ]) + ); } From 3258cd2a7f6620b73ba6024ecc45db28349bf3f4 Mon Sep 17 00:00:00 2001 From: Antoine Beyeler <49431240+abey79@users.noreply.github.com> Date: Sun, 17 Mar 2024 17:12:41 +0100 Subject: [PATCH 012/134] Fix two `ScrollArea` bugs: leaking scroll target and broken animation to target offset (#4174) This PR fixes two issues related to `ScrollArea`. 1) When a `ScrollArea` would have `drag_to_scroll` set to `false` (e.g. because some custom logic is at play or some other reason), it would not animate to the `target_offset`, effectively making `Response::scroll_to_me()` ineffective. 2) Single-direction `ScrollArea`s would leak the `scroll_target`'s other direction. In certain specific circumstances (e.g. an horizontal area nested in a vertical one, or inversely), this _could_ work as intended, but in many other cases it could cause unwanted effects. With this PR, both `scroll_target` directions are consumed by nearest enclosing `ScrollArea`, regardless of the actually enabled scroll axes. --- crates/egui/src/containers/scroll_area.rs | 89 ++++++++++++----------- 1 file changed, 47 insertions(+), 42 deletions(-) diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index 27128f67126..c35cca34225 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -553,6 +553,7 @@ impl ScrollArea { } let viewport = Rect::from_min_size(Pos2::ZERO + state.offset, inner_size); + let dt = ui.input(|i| i.stable_dt).at_most(0.1); if (scrolling_enabled && drag_to_scroll) && (state.content_is_too_large[0] || state.content_is_too_large[1]) @@ -577,48 +578,50 @@ impl ScrollArea { } } else { for d in 0..2 { - let dt = ui.input(|i| i.stable_dt).at_most(0.1); + // Kinetic scrolling + let stop_speed = 20.0; // Pixels per second. + let friction_coeff = 1000.0; // Pixels per second squared. - if let Some(scroll_target) = state.offset_target[d] { + let friction = friction_coeff * dt; + if friction > state.vel[d].abs() || state.vel[d].abs() < stop_speed { state.vel[d] = 0.0; - - if (state.offset[d] - scroll_target.target_offset).abs() < 1.0 { - // Arrived - state.offset[d] = scroll_target.target_offset; - state.offset_target[d] = None; - } else { - // Move towards target - let t = emath::interpolation_factor( - scroll_target.animation_time_span, - ui.input(|i| i.time), - dt, - emath::ease_in_ease_out, - ); - if t < 1.0 { - state.offset[d] = - emath::lerp(state.offset[d]..=scroll_target.target_offset, t); - ctx.request_repaint(); - } else { - // Arrived - state.offset[d] = scroll_target.target_offset; - state.offset_target[d] = None; - } - } } else { - // Kinetic scrolling - let stop_speed = 20.0; // Pixels per second. - let friction_coeff = 1000.0; // Pixels per second squared. + state.vel[d] -= friction * state.vel[d].signum(); + // Offset has an inverted coordinate system compared to + // the velocity, so we subtract it instead of adding it + state.offset[d] -= state.vel[d] * dt; + ctx.request_repaint(); + } + } + } + } - let friction = friction_coeff * dt; - if friction > state.vel[d].abs() || state.vel[d].abs() < stop_speed { - state.vel[d] = 0.0; - } else { - state.vel[d] -= friction * state.vel[d].signum(); - // Offset has an inverted coordinate system compared to - // the velocity, so we subtract it instead of adding it - state.offset[d] -= state.vel[d] * dt; - ctx.request_repaint(); - } + // Scroll with an animation if we have a target offset (that hasn't been cleared by the code + // above). + for d in 0..2 { + if let Some(scroll_target) = state.offset_target[d] { + state.vel[d] = 0.0; + + if (state.offset[d] - scroll_target.target_offset).abs() < 1.0 { + // Arrived + state.offset[d] = scroll_target.target_offset; + state.offset_target[d] = None; + } else { + // Move towards target + let t = emath::interpolation_factor( + scroll_target.animation_time_span, + ui.input(|i| i.time), + dt, + emath::ease_in_ease_out, + ); + if t < 1.0 { + state.offset[d] = + emath::lerp(state.offset[d]..=scroll_target.target_offset, t); + ctx.request_repaint(); + } else { + // Arrived + state.offset[d] = scroll_target.target_offset; + state.offset_target[d] = None; } } } @@ -753,11 +756,13 @@ impl Prepared { let content_size = content_ui.min_size(); for d in 0..2 { + // We always take both scroll targets regardless of which scroll axes are enabled. This + // is to avoid them leaking to other scroll areas. + let scroll_target = content_ui + .ctx() + .frame_state_mut(|state| state.scroll_target[d].take()); + if scroll_enabled[d] { - // We take the scroll target so only this ScrollArea will use it: - let scroll_target = content_ui - .ctx() - .frame_state_mut(|state| state.scroll_target[d].take()); if let Some((target_range, align)) = scroll_target { let min = content_ui.min_rect().min[d]; let clip_rect = content_ui.clip_rect(); From 820fa3c43a2d20e140bc4525e0a1405e7420bb66 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 17 Mar 2024 17:50:33 +0100 Subject: [PATCH 013/134] `Location::query_map`: support repeated key (#4183) This adds support for parsing e.g. `?foo=hello&foo=world`, returning both "hello" and "world" in `Location::query_map` --- crates/eframe/src/epi.rs | 4 ++-- crates/eframe/src/web/backend.rs | 29 ++++++++++++++++------------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/crates/eframe/src/epi.rs b/crates/eframe/src/epi.rs index a27a37335cc..5ee29e457fe 100644 --- a/crates/eframe/src/epi.rs +++ b/crates/eframe/src/epi.rs @@ -758,8 +758,8 @@ pub struct Location { /// The parsed "query" part of "www.example.com/index.html?query#fragment". /// - /// "foo=42&bar%20" is parsed as `{"foo": "42", "bar ": ""}` - pub query_map: std::collections::BTreeMap, + /// "foo=hello&bar%20&foo=world" is parsed as `{"bar ": [""], "foo": ["hello", "world"]}` + pub query_map: std::collections::BTreeMap>, /// `location.origin` /// diff --git a/crates/eframe/src/web/backend.rs b/crates/eframe/src/web/backend.rs index 1121cb43782..041fe42028a 100644 --- a/crates/eframe/src/web/backend.rs +++ b/crates/eframe/src/web/backend.rs @@ -117,21 +117,24 @@ pub fn web_location() -> epi::Location { } /// query is percent-encoded -fn parse_query_map(query: &str) -> BTreeMap { - query - .split('&') - .filter_map(|pair| { - if pair.is_empty() { - None +fn parse_query_map(query: &str) -> BTreeMap> { + let mut map: BTreeMap> = Default::default(); + + for pair in query.split('&') { + if !pair.is_empty() { + if let Some((key, value)) = pair.split_once('=') { + map.entry(percent_decode(key)) + .or_default() + .push(percent_decode(value)); } else { - Some(if let Some((key, value)) = pair.split_once('=') { - (percent_decode(key), percent_decode(value)) - } else { - (percent_decode(pair), String::new()) - }) + map.entry(percent_decode(pair)) + .or_default() + .push(String::new()); } - }) - .collect() + } + } + + map } // TODO(emilk): this test is never acgtually run, because this whole module is wasm32 only 🤦‍♂️ From cd1ed733883549bcfe52b009aed8b5c993703314 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 19 Mar 2024 18:16:04 +0100 Subject: [PATCH 014/134] Change the definition of `clicked_by` (#4192) This is a refactor on the way to add support for opening context menus on touch screens via press-and-hold. This PR changes what `InputState::button_clicked` does (it was ver badly named before), and also changes `Response::clicked_by` to no longer be true if clicking with keyboard (i.e. a widget has keyboard focus and the user presses Space or Enter). --- CHANGELOG.md | 7 ++ crates/egui/src/context.rs | 19 ++---- crates/egui/src/input_state.rs | 12 ++-- crates/egui/src/input_state/touch_state.rs | 5 +- crates/egui/src/menu.rs | 7 +- crates/egui/src/response.rs | 77 ++++++++++------------ crates/egui_demo_lib/src/demo/pan_zoom.rs | 2 +- 7 files changed, 62 insertions(+), 67 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c468fc815d..0a074c6f11e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## Unreleased + +### ⚠️ BREAKING +* `Response::clicked*` and `Response::dragged*` may lock the `Context`, so don't call it from a `Context`-locking closure. +* `Response::clicked_by` will no longer be true if clicked with keyboard. Use `Response::clicked` instead. + + ## 0.26.2 - 2024-02-14 * Avoid interacting twice when not required [#4041](https://github.com/emilk/egui/pull/4041) (thanks [@abey79](https://github.com/abey79)!) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index c1dcd5b5be9..9728281e9b7 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -1104,9 +1104,8 @@ impl Context { contains_pointer: false, hovered: false, highlighted, - clicked: Default::default(), - double_clicked: Default::default(), - triple_clicked: Default::default(), + clicked: false, + fake_primary_click: false, drag_started: false, dragged: false, drag_stopped: false, @@ -1131,7 +1130,7 @@ impl Context { && (input.key_pressed(Key::Space) || input.key_pressed(Key::Enter)) { // Space/enter works like a primary click for e.g. selected buttons - res.clicked[PointerButton::Primary as usize] = true; + res.fake_primary_click = true; } #[cfg(feature = "accesskit")] @@ -1139,7 +1138,7 @@ impl Context { && sense.click && input.has_accesskit_action_request(id, accesskit::Action::Default) { - res.clicked[PointerButton::Primary as usize] = true; + res.fake_primary_click = true; } let interaction = memory.interaction(); @@ -1157,13 +1156,9 @@ impl Context { let clicked = Some(id) == viewport.interact_widgets.clicked; for pointer_event in &input.pointer.pointer_events { - if let PointerEvent::Released { click, button } = pointer_event { - if enabled && sense.click && clicked { - if let Some(click) = click { - res.clicked[*button as usize] = true; - res.double_clicked[*button as usize] = click.is_double(); - res.triple_clicked[*button as usize] = click.is_triple(); - } + if let PointerEvent::Released { click, .. } = pointer_event { + if enabled && sense.click && clicked && click.is_some() { + res.clicked = true; } res.is_pointer_button_down_on = false; diff --git a/crates/egui/src/input_state.rs b/crates/egui/src/input_state.rs index ed29282e950..7fc29518ad1 100644 --- a/crates/egui/src/input_state.rs +++ b/crates/egui/src/input_state.rs @@ -489,11 +489,7 @@ impl InputState { /// delivers a synthetic zoom factor based on ctrl-scroll events, as a fallback. pub fn multi_touch(&self) -> Option { // In case of multiple touch devices simply pick the touch_state of the first active device - if let Some(touch_state) = self.touch_states.values().find(|t| t.is_active()) { - touch_state.info() - } else { - None - } + self.touch_states.values().find_map(|t| t.info()) } /// True if there currently are any fingers touching egui. @@ -976,11 +972,13 @@ impl PointerState { self.pointer_events.iter().any(|event| event.is_click()) } - /// Was the button given clicked this frame? + /// Was the given pointer button given clicked this frame? + /// + /// Returns true on double- and triple- clicks too. pub fn button_clicked(&self, button: PointerButton) -> bool { self.pointer_events .iter() - .any(|event| matches!(event, &PointerEvent::Pressed { button: b, .. } if button == b)) + .any(|event| matches!(event, &PointerEvent::Released { button: b, click: Some(_) } if button == b)) } /// Was the button given double clicked this frame? diff --git a/crates/egui/src/input_state/touch_state.rs b/crates/egui/src/input_state/touch_state.rs index 2ee7a91de75..2a77a4d371b 100644 --- a/crates/egui/src/input_state/touch_state.rs +++ b/crates/egui/src/input_state/touch_state.rs @@ -163,6 +163,7 @@ impl TouchState { _ => (), } } + // This needs to be called each frame, even if there are no new touch events. // Otherwise, we would send the same old delta information multiple times: self.update_gesture(time, pointer_pos); @@ -176,10 +177,6 @@ impl TouchState { } } - pub fn is_active(&self) -> bool { - self.gesture_state.is_some() - } - pub fn info(&self) -> Option { self.gesture_state.as_ref().map(|state| { // state.previous can be `None` when the number of simultaneous touches has just diff --git a/crates/egui/src/menu.rs b/crates/egui/src/menu.rs index 1f7a74fbf4d..6059236787c 100644 --- a/crates/egui/src/menu.rs +++ b/crates/egui/src/menu.rs @@ -367,6 +367,9 @@ impl MenuRoot { /// Interaction with a context menu (secondary click). fn context_interaction(response: &Response, root: &mut Option) -> MenuResponse { let response = response.interact(Sense::click()); + let hovered = response.hovered(); + let secondary_clicked = response.secondary_clicked(); + response.ctx.input(|input| { let pointer = &input.pointer; if let Some(pos) = pointer.interact_pos() { @@ -377,9 +380,9 @@ impl MenuRoot { destroy = !in_old_menu && pointer.any_pressed() && root.id == response.id; } if !in_old_menu { - if response.hovered() && response.secondary_clicked() { + if hovered && secondary_clicked { return MenuResponse::Create(pos, response.id); - } else if (response.hovered() && pointer.primary_down()) || destroy { + } else if destroy || hovered && pointer.primary_down() { return MenuResponse::Close; } } diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index ddd683c4c8f..be65b8445d6 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -3,7 +3,6 @@ use std::{any::Any, sync::Arc}; use crate::{ emath::{Align, Pos2, Rect, Vec2}, menu, Context, CursorIcon, Id, LayerId, PointerButton, Sense, Ui, WidgetRect, WidgetText, - NUM_POINTER_BUTTONS, }; // ---------------------------------------------------------------------------- @@ -15,7 +14,10 @@ use crate::{ /// /// Whenever something gets added to a [`Ui`], a [`Response`] object is returned. /// [`ui.add`] returns a [`Response`], as does [`ui.button`], and all similar shortcuts. -// TODO(emilk): we should be using bit sets instead of so many bools +/// +/// ⚠️ The `Response` contains a clone of [`Context`], and many methods lock the `Context`. +/// It can therefor be a deadlock to use `Context` from within a context-locking closures, +/// such as [`Context::input`]. #[derive(Clone, Debug)] pub struct Response { // CONTEXT: @@ -69,18 +71,23 @@ pub struct Response { #[doc(hidden)] pub highlighted: bool, - /// The pointer clicked this thing this frame. - #[doc(hidden)] - pub clicked: [bool; NUM_POINTER_BUTTONS], - - // TODO(emilk): `released` for sliders - /// The thing was double-clicked. + /// This widget was clicked this frame. + /// + /// Which pointer and how many times we don't know, + /// and ask [`crate::InputState`] about at runtime. + /// + /// This is only set to true if the widget was clicked + /// by an actual mouse. #[doc(hidden)] - pub double_clicked: [bool; NUM_POINTER_BUTTONS], + pub clicked: bool, - /// The thing was triple-clicked. + /// This widget should act as if clicked due + /// to something else than a click. + /// + /// This is set to true if the widget has keyboard focus and + /// the user hit the Space or Enter key. #[doc(hidden)] - pub triple_clicked: [bool; NUM_POINTER_BUTTONS], + pub fake_primary_click: bool, /// The widget started being dragged this frame. #[doc(hidden)] @@ -118,55 +125,62 @@ impl Response { /// A click is registered when the mouse or touch is released within /// a certain amount of time and distance from when and where it was pressed. /// + /// This will also return true if the widget was clicked via accessibility integration, + /// or if the widget had keyboard focus and the use pressed Space/Enter. + /// /// Note that the widget must be sensing clicks with [`Sense::click`]. /// [`crate::Button`] senses clicks; [`crate::Label`] does not (unless you call [`crate::Label::sense`]). /// /// You can use [`Self::interact`] to sense more things *after* adding a widget. #[inline(always)] pub fn clicked(&self) -> bool { - self.clicked[PointerButton::Primary as usize] + self.fake_primary_click || self.clicked_by(PointerButton::Primary) } - /// Returns true if this widget was clicked this frame by the given button. + /// Returns true if this widget was clicked this frame by the given mouse button. + /// + /// This will NOT return true if the widget was "clicked" via + /// some accessibility integration, or if the widget had keyboard focus and the + /// user pressed Space/Enter. For that, use [`Self::clicked`] instead. #[inline] pub fn clicked_by(&self, button: PointerButton) -> bool { - self.clicked[button as usize] + self.clicked && self.ctx.input(|i| i.pointer.button_clicked(button)) } /// Returns true if this widget was clicked this frame by the secondary mouse button (e.g. the right mouse button). #[inline] pub fn secondary_clicked(&self) -> bool { - self.clicked[PointerButton::Secondary as usize] + self.clicked_by(PointerButton::Secondary) } /// Returns true if this widget was clicked this frame by the middle mouse button. #[inline] pub fn middle_clicked(&self) -> bool { - self.clicked[PointerButton::Middle as usize] + self.clicked_by(PointerButton::Middle) } /// Returns true if this widget was double-clicked this frame by the primary button. #[inline] pub fn double_clicked(&self) -> bool { - self.double_clicked[PointerButton::Primary as usize] + self.double_clicked_by(PointerButton::Primary) } /// Returns true if this widget was triple-clicked this frame by the primary button. #[inline] pub fn triple_clicked(&self) -> bool { - self.triple_clicked[PointerButton::Primary as usize] + self.triple_clicked_by(PointerButton::Primary) } /// Returns true if this widget was double-clicked this frame by the given button. #[inline] pub fn double_clicked_by(&self, button: PointerButton) -> bool { - self.double_clicked[button as usize] + self.clicked && self.ctx.input(|i| i.pointer.button_double_clicked(button)) } /// Returns true if this widget was triple-clicked this frame by the given button. #[inline] pub fn triple_clicked_by(&self, button: PointerButton) -> bool { - self.triple_clicked[button as usize] + self.clicked && self.ctx.input(|i| i.pointer.button_triple_clicked(button)) } /// `true` if there was a click *outside* this widget this frame. @@ -917,27 +931,8 @@ impl Response { contains_pointer: self.contains_pointer || other.contains_pointer, hovered: self.hovered || other.hovered, highlighted: self.highlighted || other.highlighted, - clicked: [ - self.clicked[0] || other.clicked[0], - self.clicked[1] || other.clicked[1], - self.clicked[2] || other.clicked[2], - self.clicked[3] || other.clicked[3], - self.clicked[4] || other.clicked[4], - ], - double_clicked: [ - self.double_clicked[0] || other.double_clicked[0], - self.double_clicked[1] || other.double_clicked[1], - self.double_clicked[2] || other.double_clicked[2], - self.double_clicked[3] || other.double_clicked[3], - self.double_clicked[4] || other.double_clicked[4], - ], - triple_clicked: [ - self.triple_clicked[0] || other.triple_clicked[0], - self.triple_clicked[1] || other.triple_clicked[1], - self.triple_clicked[2] || other.triple_clicked[2], - self.triple_clicked[3] || other.triple_clicked[3], - self.triple_clicked[4] || other.triple_clicked[4], - ], + clicked: self.clicked || other.clicked, + fake_primary_click: self.fake_primary_click || other.fake_primary_click, drag_started: self.drag_started || other.drag_started, dragged: self.dragged || other.dragged, drag_stopped: self.drag_stopped || other.drag_stopped, diff --git a/crates/egui_demo_lib/src/demo/pan_zoom.rs b/crates/egui_demo_lib/src/demo/pan_zoom.rs index 08829d6d5e6..40323346887 100644 --- a/crates/egui_demo_lib/src/demo/pan_zoom.rs +++ b/crates/egui_demo_lib/src/demo/pan_zoom.rs @@ -11,7 +11,7 @@ impl Eq for PanZoom {} impl super::Demo for PanZoom { fn name(&self) -> &'static str { - "🗖 Pan Zoom" + "🔍 Pan Zoom" } fn show(&mut self, ctx: &egui::Context, open: &mut bool) { From d449cb1d488cbc10915a3c720a232c01c1901561 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 20 Mar 2024 11:49:17 +0100 Subject: [PATCH 015/134] On touch screens, press-and-hold equals a secondary click (#4195) * Closes https://github.com/emilk/egui/issues/3444 * Closes https://github.com/emilk/egui/issues/865 On a touch screen, if you press down on a widget and hold for 0.6 seconds (`MAX_CLICK_DURATION`), it will now trigger a secondary click, i.e. `Response::secondary_clicked` will be `true`. This means you can now open context menus on touch screens. --- crates/egui/src/context.rs | 48 +++++----- crates/egui/src/frame_state.rs | 6 +- crates/egui/src/input_state.rs | 57 +++++++++--- crates/egui/src/interaction.rs | 47 +++++++++- crates/egui/src/introspection.rs | 1 - crates/egui/src/memory.rs | 88 ++++++++++--------- crates/egui/src/response.rs | 20 ++++- crates/egui_demo_lib/src/demo/context_menu.rs | 6 +- crates/egui_demo_lib/src/demo/tests.rs | 4 + examples/custom_keypad/src/keypad.rs | 2 +- 10 files changed, 187 insertions(+), 92 deletions(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 9728281e9b7..d0d6b9ca266 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -2,7 +2,6 @@ use std::{borrow::Cow, cell::RefCell, panic::Location, sync::Arc, time::Duration}; -use ahash::HashMap; use epaint::{ emath::TSTransform, mutex::*, stats::*, text::Fonts, util::OrderedFloat, TessellationOptions, *, }; @@ -421,18 +420,17 @@ impl ContextImpl { // but the `screen_rect` is the most important part. } } - let pixels_per_point = self.memory.options.zoom_factor - * new_raw_input - .viewport() - .native_pixels_per_point - .unwrap_or(1.0); + let native_pixels_per_point = new_raw_input + .viewport() + .native_pixels_per_point + .unwrap_or(1.0); + let pixels_per_point = self.memory.options.zoom_factor * native_pixels_per_point; let all_viewport_ids: ViewportIdSet = self.all_viewport_ids(); let viewport = self.viewports.entry(self.viewport_id()).or_default(); - self.memory - .begin_frame(&viewport.input, &new_raw_input, &all_viewport_ids); + self.memory.begin_frame(&new_raw_input, &all_viewport_ids); viewport.input = std::mem::take(&mut viewport.input).begin_frame( new_raw_input, @@ -440,17 +438,12 @@ impl ContextImpl { pixels_per_point, ); - viewport.frame_state.begin_frame(&viewport.input); + let screen_rect = viewport.input.screen_rect; + + viewport.frame_state.begin_frame(screen_rect); { - let area_order: HashMap = self - .memory - .areas() - .order() - .iter() - .enumerate() - .map(|(i, id)| (*id, i)) - .collect(); + let area_order = self.memory.areas().order_map(); let mut layers: Vec = viewport.widgets_prev_frame.layer_ids().collect(); @@ -488,7 +481,6 @@ impl ContextImpl { } // Ensure we register the background area so panels and background ui can catch clicks: - let screen_rect = viewport.input.screen_rect(); self.memory.areas_mut().set_state( LayerId::background(), containers::area::State { @@ -1106,6 +1098,7 @@ impl Context { highlighted, clicked: false, fake_primary_click: false, + long_touched: false, drag_started: false, dragged: false, drag_stopped: false, @@ -1141,6 +1134,10 @@ impl Context { res.fake_primary_click = true; } + if enabled && sense.click && Some(id) == viewport.interact_widgets.long_touched { + res.long_touched = true; + } + let interaction = memory.interaction(); res.is_pointer_button_down_on = interaction.potential_click_id == Some(id) @@ -1168,7 +1165,8 @@ impl Context { // is_pointer_button_down_on is false when released, but we want interact_pointer_pos // to still work. - let is_interacted_with = res.is_pointer_button_down_on || clicked || res.drag_stopped; + let is_interacted_with = + res.is_pointer_button_down_on || res.long_touched || clicked || res.drag_stopped; if is_interacted_with { res.interact_pointer_pos = input.pointer.interact_pos(); if let (Some(transform), Some(pos)) = ( @@ -1179,7 +1177,7 @@ impl Context { } } - if input.pointer.any_down() && !res.is_pointer_button_down_on { + if input.pointer.any_down() && !is_interacted_with { // We don't hover widgets while interacting with *other* widgets: res.hovered = false; } @@ -1847,6 +1845,7 @@ impl Context { let interact_widgets = self.write(|ctx| ctx.viewport().interact_widgets.clone()); let InteractionSnapshot { clicked, + long_touched: _, drag_started: _, dragged, drag_stopped: _, @@ -1956,7 +1955,10 @@ impl ContextImpl { }) .collect() }; - let focus_id = self.memory.focus().map_or(root_id, |id| id.accesskit_id()); + let focus_id = self + .memory + .focused() + .map_or(root_id, |id| id.accesskit_id()); platform_output.accesskit_update = Some(accesskit::TreeUpdate { nodes, tree: Some(accesskit::Tree::new(root_id)), @@ -2221,7 +2223,7 @@ impl Context { /// If `true`, egui is currently listening on text input (e.g. typing text in a [`TextEdit`]). pub fn wants_keyboard_input(&self) -> bool { - self.memory(|m| m.interaction().focus.focused().is_some()) + self.memory(|m| m.focused().is_some()) } /// Highlight this widget, to make it look like it is hovered, even if it isn't. @@ -2481,7 +2483,7 @@ impl Context { .on_hover_text("Is egui currently listening for text input?"); ui.label(format!( "Keyboard focus widget: {}", - self.memory(|m| m.interaction().focus.focused()) + self.memory(|m| m.focused()) .as_ref() .map(Id::short_debug_format) .unwrap_or_default() diff --git a/crates/egui/src/frame_state.rs b/crates/egui/src/frame_state.rs index d94f1222aa6..87074c2a725 100644 --- a/crates/egui/src/frame_state.rs +++ b/crates/egui/src/frame_state.rs @@ -75,7 +75,7 @@ impl Default for FrameState { } impl FrameState { - pub(crate) fn begin_frame(&mut self, input: &InputState) { + pub(crate) fn begin_frame(&mut self, screen_rect: Rect) { crate::profile_function!(); let Self { used_ids, @@ -94,8 +94,8 @@ impl FrameState { } = self; used_ids.clear(); - *available_rect = input.screen_rect(); - *unused_rect = input.screen_rect(); + *available_rect = screen_rect; + *unused_rect = screen_rect; *used_by_panels = Rect::NOTHING; *tooltip_state = None; *scroll_target = [None, None]; diff --git a/crates/egui/src/input_state.rs b/crates/egui/src/input_state.rs index 7fc29518ad1..8809cdb8d1f 100644 --- a/crates/egui/src/input_state.rs +++ b/crates/egui/src/input_state.rs @@ -11,8 +11,13 @@ use touch_state::TouchState; /// If the pointer moves more than this, it won't become a click (but it is still a drag) const MAX_CLICK_DIST: f32 = 6.0; // TODO(emilk): move to settings -/// If the pointer is down for longer than this, it won't become a click (but it is still a drag) -const MAX_CLICK_DURATION: f64 = 0.6; // TODO(emilk): move to settings +/// If the pointer is down for longer than this it will no longer register as a click. +/// +/// If a touch is held for this many seconds while still, +/// then it will register as a "long-touch" which is equivalent to a secondary click. +/// +/// This is to support "press and hold for context menu" on touch screens. +const MAX_CLICK_DURATION: f64 = 0.8; // TODO(emilk): move to settings /// The new pointer press must come within this many seconds from previous pointer release const MAX_DOUBLE_CLICK_DELAY: f64 = 0.3; // TODO(emilk): move to settings @@ -544,6 +549,14 @@ impl InputState { .cloned() .collect() } + + /// A long press is something we detect on touch screens + /// to trigger a secondary click (context menu). + /// + /// Returns `true` only on one frame. + pub(crate) fn is_long_touch(&self) -> bool { + self.any_touches() && self.pointer.is_long_press() + } } // ---------------------------------------------------------------------------- @@ -651,6 +664,8 @@ pub struct PointerState { pub(crate) has_moved_too_much_for_a_click: bool, /// Did [`Self::is_decidedly_dragging`] go from `false` to `true` this frame? + /// + /// This could also be the trigger point for a long-touch. pub(crate) started_decidedly_dragging: bool, /// When did the pointer get click last? @@ -751,6 +766,7 @@ impl PointerState { button, }); } else { + // Released let clicked = self.could_any_button_be_click(); let click = if clicked { @@ -1027,21 +1043,21 @@ impl PointerState { /// /// See also [`Self::is_decidedly_dragging`]. pub fn could_any_button_be_click(&self) -> bool { - if !self.any_down() { - return false; - } - - if self.has_moved_too_much_for_a_click { - return false; - } - - if let Some(press_start_time) = self.press_start_time { - if self.time - press_start_time > MAX_CLICK_DURATION { + if self.any_down() || self.any_released() { + if self.has_moved_too_much_for_a_click { return false; } - } - true + if let Some(press_start_time) = self.press_start_time { + if self.time - press_start_time > MAX_CLICK_DURATION { + return false; + } + } + + true + } else { + false + } } /// Just because the mouse is down doesn't mean we are dragging. @@ -1060,6 +1076,19 @@ impl PointerState { && !self.any_click() } + /// A long press is something we detect on touch screens + /// to trigger a secondary click (context menu). + /// + /// Returns `true` only on one frame. + pub(crate) fn is_long_press(&self) -> bool { + self.started_decidedly_dragging + && !self.has_moved_too_much_for_a_click + && self.button_down(PointerButton::Primary) + && self.press_start_time.map_or(false, |press_start_time| { + self.time - press_start_time > MAX_CLICK_DURATION + }) + } + /// Is the primary button currently down? #[inline(always)] pub fn primary_down(&self) -> bool { diff --git a/crates/egui/src/interaction.rs b/crates/egui/src/interaction.rs index 20d54887018..d5812d4d7c2 100644 --- a/crates/egui/src/interaction.rs +++ b/crates/egui/src/interaction.rs @@ -14,6 +14,10 @@ pub struct InteractionSnapshot { /// The widget that got clicked this frame. pub clicked: Option, + /// This widget was long-pressed on a touch screen, + /// so trigger a secondary click on it (context menu). + pub long_touched: Option, + /// Drag started on this widget this frame. /// /// This will also be found in `dragged` this frame. @@ -56,6 +60,7 @@ impl InteractionSnapshot { pub fn ui(&self, ui: &mut crate::Ui) { let Self { clicked, + long_touched, drag_started, dragged, drag_stopped, @@ -74,6 +79,10 @@ impl InteractionSnapshot { id_ui(ui, clicked); ui.end_row(); + ui.label("long_touched"); + id_ui(ui, long_touched); + ui.end_row(); + ui.label("drag_started"); id_ui(ui, drag_started); ui.end_row(); @@ -123,6 +132,21 @@ pub(crate) fn interact( let mut clicked = None; let mut dragged = prev_snapshot.dragged; + let mut long_touched = None; + + if input.is_long_touch() { + // We implement "press-and-hold for context menu" on touch screens here + if let Some(widget) = interaction + .potential_click_id + .and_then(|id| widgets.get(id)) + { + dragged = None; + clicked = Some(widget.id); + long_touched = Some(widget.id); + interaction.potential_click_id = None; + interaction.potential_drag_id = None; + } + } // Note: in the current code a press-release in the same frame is NOT considered a drag. for pointer_event in &input.pointer.pointer_events { @@ -142,7 +166,7 @@ pub(crate) fn interact( } PointerEvent::Released { click, button: _ } => { - if click.is_some() { + if click.is_some() && !input.pointer.is_decidedly_dragging() { if let Some(widget) = interaction .potential_click_id .and_then(|id| widgets.get(id)) @@ -179,6 +203,15 @@ pub(crate) fn interact( } } + if !input.pointer.could_any_button_be_click() { + interaction.potential_click_id = None; + } + + if !input.pointer.any_down() || input.pointer.latest_pos().is_none() { + interaction.potential_click_id = None; + interaction.potential_drag_id = None; + } + // ------------------------------------------------------------------------ let drag_changed = dragged != prev_snapshot.dragged; @@ -201,9 +234,14 @@ pub(crate) fn interact( .map(|w| w.id) .collect(); - let hovered = if clicked.is_some() || dragged.is_some() { - // If currently clicking or dragging, nothing else is hovered. - clicked.iter().chain(&dragged).copied().collect() + let hovered = if clicked.is_some() || dragged.is_some() || long_touched.is_some() { + // If currently clicking or dragging, only that and nothing else is hovered. + clicked + .iter() + .chain(&dragged) + .chain(&long_touched) + .copied() + .collect() } else if hits.click.is_some() || hits.drag.is_some() { // We are hovering over an interactive widget or two. hits.click.iter().chain(&hits.drag).map(|w| w.id).collect() @@ -220,6 +258,7 @@ pub(crate) fn interact( InteractionSnapshot { clicked, + long_touched, drag_started, dragged, drag_stopped, diff --git a/crates/egui/src/introspection.rs b/crates/egui/src/introspection.rs index 240ff6974a5..9c738153873 100644 --- a/crates/egui/src/introspection.rs +++ b/crates/egui/src/introspection.rs @@ -194,7 +194,6 @@ impl Widget for &memory::InteractionState { let memory::InteractionState { potential_click_id, potential_drag_id, - focus: _, } = self; ui.vertical(|ui| { diff --git a/crates/egui/src/memory.rs b/crates/egui/src/memory.rs index fb18258eb12..3ea3a19fe28 100644 --- a/crates/egui/src/memory.rs +++ b/crates/egui/src/memory.rs @@ -4,7 +4,7 @@ use ahash::HashMap; use epaint::emath::TSTransform; use crate::{ - area, vec2, EventFilter, Id, IdMap, LayerId, Order, Pos2, Rangef, Rect, Style, Vec2, + area, vec2, EventFilter, Id, IdMap, LayerId, Order, Pos2, Rangef, RawInput, Rect, Style, Vec2, ViewportId, ViewportIdMap, ViewportIdSet, }; @@ -95,6 +95,9 @@ pub struct Memory { #[cfg_attr(feature = "persistence", serde(skip))] pub(crate) interactions: ViewportIdMap, + + #[cfg_attr(feature = "persistence", serde(skip))] + pub(crate) focus: ViewportIdMap, } impl Default for Memory { @@ -105,6 +108,7 @@ impl Default for Memory { caches: Default::default(), new_font_definitions: Default::default(), interactions: Default::default(), + focus: Default::default(), viewport_id: Default::default(), areas: Default::default(), layer_transforms: Default::default(), @@ -308,8 +312,6 @@ pub(crate) struct InteractionState { /// as that can only happen after the mouse has moved a bit /// (at least if the widget is interesated in both clicks and drags). pub potential_drag_id: Option, - - pub focus: Focus, } /// Keeps tracks of what widget has keyboard focus @@ -362,24 +364,6 @@ impl InteractionState { pub fn is_using_pointer(&self) -> bool { self.potential_click_id.is_some() || self.potential_drag_id.is_some() } - - fn begin_frame( - &mut self, - prev_input: &crate::input_state::InputState, - new_input: &crate::data::input::RawInput, - ) { - if !prev_input.pointer.could_any_button_be_click() { - self.potential_click_id = None; - } - - if !prev_input.pointer.any_down() || prev_input.pointer.latest_pos().is_none() { - // pointer button was not down last frame - self.potential_click_id = None; - self.potential_drag_id = None; - } - - self.focus.begin_frame(new_input); - } } impl Focus { @@ -603,30 +587,29 @@ impl Focus { } impl Memory { - pub(crate) fn begin_frame( - &mut self, - prev_input: &crate::input_state::InputState, - new_input: &crate::data::input::RawInput, - viewports: &ViewportIdSet, - ) { + pub(crate) fn begin_frame(&mut self, new_raw_input: &RawInput, viewports: &ViewportIdSet) { crate::profile_function!(); + self.viewport_id = new_raw_input.viewport_id; + // Cleanup self.interactions.retain(|id, _| viewports.contains(id)); self.areas.retain(|id, _| viewports.contains(id)); - self.viewport_id = new_input.viewport_id; - self.interactions + self.areas.entry(self.viewport_id).or_default(); + + // self.interactions is handled elsewhere + + self.focus .entry(self.viewport_id) .or_default() - .begin_frame(prev_input, new_input); - self.areas.entry(self.viewport_id).or_default(); + .begin_frame(new_raw_input); } pub(crate) fn end_frame(&mut self, used_ids: &IdMap) { self.caches.update(); self.areas_mut().end_frame(); - self.interaction_mut().focus.end_frame(used_ids); + self.focus_mut().end_frame(used_ids); } pub(crate) fn set_viewport_id(&mut self, viewport_id: ViewportId) { @@ -656,7 +639,7 @@ impl Memory { } pub(crate) fn had_focus_last_frame(&self, id: Id) -> bool { - self.interaction().focus.id_previous_frame == Some(id) + self.focus().id_previous_frame == Some(id) } /// True if the given widget had keyboard focus last frame, but not this one. @@ -677,12 +660,12 @@ impl Memory { /// from the window and back. #[inline(always)] pub fn has_focus(&self, id: Id) -> bool { - self.interaction().focus.focused() == Some(id) + self.focused() == Some(id) } /// Which widget has keyboard focus? - pub fn focus(&self) -> Option { - self.interaction().focus.focused() + pub fn focused(&self) -> Option { + self.focus().focused() } /// Set an event filter for a widget. @@ -693,7 +676,7 @@ impl Memory { /// You must first give focus to the widget before calling this. pub fn set_focus_lock_filter(&mut self, id: Id, event_filter: EventFilter) { if self.had_focus_last_frame(id) && self.has_focus(id) { - if let Some(focused) = &mut self.interaction_mut().focus.focused_widget { + if let Some(focused) = &mut self.focus_mut().focused_widget { if focused.id == id { focused.filter = event_filter; } @@ -705,16 +688,16 @@ impl Memory { /// See also [`crate::Response::request_focus`]. #[inline(always)] pub fn request_focus(&mut self, id: Id) { - self.interaction_mut().focus.focused_widget = Some(FocusWidget::new(id)); + self.focus_mut().focused_widget = Some(FocusWidget::new(id)); } /// Surrender keyboard focus for a specific widget. /// See also [`crate::Response::surrender_focus`]. #[inline(always)] pub fn surrender_focus(&mut self, id: Id) { - let interaction = self.interaction_mut(); - if interaction.focus.focused() == Some(id) { - interaction.focus.focused_widget = None; + let focus = self.focus_mut(); + if focus.focused() == Some(id) { + focus.focused_widget = None; } } @@ -727,13 +710,13 @@ impl Memory { /// and rendered correctly in a single frame. #[inline(always)] pub fn interested_in_focus(&mut self, id: Id) { - self.interaction_mut().focus.interested_in_focus(id); + self.focus_mut().interested_in_focus(id); } /// Stop editing of active [`TextEdit`](crate::TextEdit) (if any). #[inline(always)] pub fn stop_text_input(&mut self) { - self.interaction_mut().focus.focused_widget = None; + self.focus_mut().focused_widget = None; } /// Is any widget being dragged? @@ -813,6 +796,16 @@ impl Memory { pub(crate) fn interaction_mut(&mut self) -> &mut InteractionState { self.interactions.entry(self.viewport_id).or_default() } + + pub(crate) fn focus(&self) -> &Focus { + self.focus + .get(&self.viewport_id) + .expect("Failed to get focus") + } + + pub(crate) fn focus_mut(&mut self) -> &mut Focus { + self.focus.entry(self.viewport_id).or_default() + } } /// ## Popups @@ -908,6 +901,15 @@ impl Areas { &self.order } + /// For each layer, which order is it in [`Self::order`]? + pub(crate) fn order_map(&self) -> HashMap { + self.order + .iter() + .enumerate() + .map(|(i, id)| (*id, i)) + .collect() + } + pub(crate) fn set_state(&mut self, layer_id: LayerId, state: area::State) { self.visible_current_frame.insert(layer_id); self.areas.insert(layer_id.id, state); diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index be65b8445d6..32c07ed7b1a 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -89,6 +89,10 @@ pub struct Response { #[doc(hidden)] pub fake_primary_click: bool, + /// This widget was long-pressed on a touch screen to simulate a secondary click. + #[doc(hidden)] + pub long_touched: bool, + /// The widget started being dragged this frame. #[doc(hidden)] pub drag_started: bool, @@ -142,15 +146,28 @@ impl Response { /// This will NOT return true if the widget was "clicked" via /// some accessibility integration, or if the widget had keyboard focus and the /// user pressed Space/Enter. For that, use [`Self::clicked`] instead. + /// + /// This will likewise ignore the press-and-hold action on touch screens. + /// Use [`Self::secondary_clicked`] instead to also detect that. #[inline] pub fn clicked_by(&self, button: PointerButton) -> bool { self.clicked && self.ctx.input(|i| i.pointer.button_clicked(button)) } /// Returns true if this widget was clicked this frame by the secondary mouse button (e.g. the right mouse button). + /// + /// This also returns true if the widget was pressed-and-held on a touch screen. #[inline] pub fn secondary_clicked(&self) -> bool { - self.clicked_by(PointerButton::Secondary) + self.long_touched || self.clicked_by(PointerButton::Secondary) + } + + /// Was this long-pressed on a touch screen? + /// + /// Usually you want to check [`Self::secondary_clicked`] instead. + #[inline] + pub fn long_touched(&self) -> bool { + self.long_touched } /// Returns true if this widget was clicked this frame by the middle mouse button. @@ -933,6 +950,7 @@ impl Response { highlighted: self.highlighted || other.highlighted, clicked: self.clicked || other.clicked, fake_primary_click: self.fake_primary_click || other.fake_primary_click, + long_touched: self.long_touched || other.long_touched, drag_started: self.drag_started || other.drag_started, dragged: self.dragged || other.dragged, drag_stopped: self.drag_stopped || other.drag_stopped, diff --git a/crates/egui_demo_lib/src/demo/context_menu.rs b/crates/egui_demo_lib/src/demo/context_menu.rs index ffcc73379d8..1b71eb4f787 100644 --- a/crates/egui_demo_lib/src/demo/context_menu.rs +++ b/crates/egui_demo_lib/src/demo/context_menu.rs @@ -80,6 +80,8 @@ impl super::View for ContextMenus { ui.label("Right-click plot to edit it!"); ui.horizontal(|ui| { self.example_plot(ui).context_menu(|ui| { + ui.set_min_width(220.0); + ui.menu_button("Plot", |ui| { if ui.radio_value(&mut self.plot, Plot::Sin, "Sin").clicked() || ui @@ -96,12 +98,12 @@ impl super::View for ContextMenus { ui.add( egui::DragValue::new(&mut self.width) .speed(1.0) - .prefix("Width:"), + .prefix("Width: "), ); ui.add( egui::DragValue::new(&mut self.height) .speed(1.0) - .prefix("Height:"), + .prefix("Height: "), ); ui.end_row(); ui.checkbox(&mut self.show_axes[0], "x-Axis"); diff --git a/crates/egui_demo_lib/src/demo/tests.rs b/crates/egui_demo_lib/src/demo/tests.rs index 2dd6ea64a86..8d45de1e15b 100644 --- a/crates/egui_demo_lib/src/demo/tests.rs +++ b/crates/egui_demo_lib/src/demo/tests.rs @@ -486,6 +486,10 @@ fn response_summary(response: &egui::Response, show_hovers: bool) -> String { } } + if response.long_touched() { + writeln!(new_info, "Clicked with long-press").ok(); + } + new_info } diff --git a/examples/custom_keypad/src/keypad.rs b/examples/custom_keypad/src/keypad.rs index 81800f47d84..d4d0cb0d855 100644 --- a/examples/custom_keypad/src/keypad.rs +++ b/examples/custom_keypad/src/keypad.rs @@ -174,7 +174,7 @@ impl Keypad { pub fn show(&self, ctx: &egui::Context) { let (focus, mut state) = ctx.memory(|m| { ( - m.focus(), + m.focused(), m.data.get_temp::(self.id).unwrap_or_default(), ) }); From 1b34289608105e6a7e8585462e602e7a9a94dd85 Mon Sep 17 00:00:00 2001 From: rustbasic <127506429+rustbasic@users.noreply.github.com> Date: Wed, 20 Mar 2024 20:55:44 +0900 Subject: [PATCH 016/134] Fix bug in `Context::parent_viewport_id` (#4190) * Closes #4084 Fix : Display parent viewport id Issues --- crates/egui/src/context.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index d0d6b9ca266..ea61cd7d034 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -592,11 +592,11 @@ impl ContextImpl { /// /// For the root viewport this will return [`ViewportId::ROOT`]. pub(crate) fn parent_viewport_id(&self) -> ViewportId { - self.viewport_stack - .last() - .copied() - .unwrap_or_default() - .parent + let viewport_id = self.viewport_id(); + *self + .viewport_parents + .get(&viewport_id) + .unwrap_or(&ViewportId::ROOT) } fn all_viewport_ids(&self) -> ViewportIdSet { From 8d47ab8bb966c8ee2ad461b5676857647d2871b4 Mon Sep 17 00:00:00 2001 From: ming08108 Date: Wed, 20 Mar 2024 07:06:45 -0500 Subject: [PATCH 017/134] Don't clear modifier state on focus change (#4157) I believe that the underlying issue that caused the stuck modifier keys was resolved in the 0.29 winit keyboard refactor. This probably needs to tested on other desktop platforms however since I am only able to test this on windows 11. * Closes --- crates/egui-winit/src/lib.rs | 3 --- crates/egui/src/input_state.rs | 16 +--------------- 2 files changed, 1 insertion(+), 18 deletions(-) diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index c02fd8d275a..b6d02cdf12c 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -376,9 +376,6 @@ impl State { } WindowEvent::Focused(focused) => { self.egui_input.focused = *focused; - // We will not be given a KeyboardInput event when the modifiers are released while - // the window does not have focus. Unset all modifier state to be safe. - self.egui_input.modifiers = egui::Modifiers::default(); self.egui_input .events .push(egui::Event::WindowFocused(*focused)); diff --git a/crates/egui/src/input_state.rs b/crates/egui/src/input_state.rs index 8809cdb8d1f..2a196c86528 100644 --- a/crates/egui/src/input_state.rs +++ b/crates/egui/src/input_state.rs @@ -249,20 +249,6 @@ impl InputState { } } - let mut modifiers = new.modifiers; - - let focused_changed = self.focused != new.focused - || new - .events - .iter() - .any(|e| matches!(e, Event::WindowFocused(_))); - if focused_changed { - // It is very common for keys to become stuck when we alt-tab, or a save-dialog opens by Ctrl+S. - // Therefore we clear all the modifiers and down keys here to avoid that. - modifiers = Default::default(); - keys_down = Default::default(); - } - Self { pointer, touch_states: self.touch_states, @@ -278,7 +264,7 @@ impl InputState { predicted_dt: new.predicted_dt, stable_dt, focused: new.focused, - modifiers, + modifiers: new.modifiers, keys_down, events: new.events.clone(), // TODO(emilk): remove clone() and use raw.events raw: new, From bce257adb3d58f615ab8c5ef9d91815c8811ad51 Mon Sep 17 00:00:00 2001 From: Eris <50041841+TheTacBanana@users.noreply.github.com> Date: Wed, 20 Mar 2024 12:23:21 +0000 Subject: [PATCH 018/134] `epaint`: Add `EllipseShape` (#4122) Adds an Ellipse shape draw-able with the Painter, brings egui closer to the SVG Specification. I've done some optimization towards using less vertices and doing less calculations. ~~Currently the vertices are evenly distributed. It's possible this could be optimized further taking into account the gradient and increasing the concentration of vertices where the change in gradient is larger.~~ ![EllipseTall](https://github.com/emilk/egui/assets/50041841/b105230c-ce68-49c1-b162-d1f066bf9d6a) ![EllipseWide](https://github.com/emilk/egui/assets/50041841/6f9106d2-75cb-4f2a-b0c2-039b3aadec86) --- crates/epaint/src/lib.rs | 4 +- crates/epaint/src/shape.rs | 74 ++++++++++++++++++++++++++++ crates/epaint/src/shape_transform.rs | 6 +++ crates/epaint/src/stats.rs | 1 + crates/epaint/src/tessellator.rs | 72 ++++++++++++++++++++++++++- 5 files changed, 154 insertions(+), 3 deletions(-) diff --git a/crates/epaint/src/lib.rs b/crates/epaint/src/lib.rs index c83be860a2a..8746d5647c3 100644 --- a/crates/epaint/src/lib.rs +++ b/crates/epaint/src/lib.rs @@ -47,8 +47,8 @@ pub use { mesh::{Mesh, Mesh16, Vertex}, shadow::Shadow, shape::{ - CircleShape, PaintCallback, PaintCallbackInfo, PathShape, RectShape, Rounding, Shape, - TextShape, + CircleShape, EllipseShape, PaintCallback, PaintCallbackInfo, PathShape, RectShape, + Rounding, Shape, TextShape, }, stats::PaintStats, stroke::Stroke, diff --git a/crates/epaint/src/shape.rs b/crates/epaint/src/shape.rs index fefae6c93a1..3c6f1703d17 100644 --- a/crates/epaint/src/shape.rs +++ b/crates/epaint/src/shape.rs @@ -30,6 +30,9 @@ pub enum Shape { /// Circle with optional outline and fill. Circle(CircleShape), + /// Ellipse with optional outline and fill. + Ellipse(EllipseShape), + /// A line between two points. LineSegment { points: [Pos2; 2], stroke: Stroke }, @@ -236,6 +239,16 @@ impl Shape { Self::Circle(CircleShape::stroke(center, radius, stroke)) } + #[inline] + pub fn ellipse_filled(center: Pos2, radius: Vec2, fill_color: impl Into) -> Self { + Self::Ellipse(EllipseShape::filled(center, radius, fill_color)) + } + + #[inline] + pub fn ellipse_stroke(center: Pos2, radius: Vec2, stroke: impl Into) -> Self { + Self::Ellipse(EllipseShape::stroke(center, radius, stroke)) + } + #[inline] pub fn rect_filled( rect: Rect, @@ -324,6 +337,7 @@ impl Shape { rect } Self::Circle(circle_shape) => circle_shape.visual_bounding_rect(), + Self::Ellipse(ellipse_shape) => ellipse_shape.visual_bounding_rect(), Self::LineSegment { points, stroke } => { if stroke.is_empty() { Rect::NOTHING @@ -388,6 +402,11 @@ impl Shape { circle_shape.radius *= transform.scaling; circle_shape.stroke.width *= transform.scaling; } + Self::Ellipse(ellipse_shape) => { + ellipse_shape.center = transform * ellipse_shape.center; + ellipse_shape.radius *= transform.scaling; + ellipse_shape.stroke.width *= transform.scaling; + } Self::LineSegment { points, stroke } => { for p in points { *p = transform * *p; @@ -497,6 +516,61 @@ impl From for Shape { // ---------------------------------------------------------------------------- +/// How to paint an ellipse. +#[derive(Copy, Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub struct EllipseShape { + pub center: Pos2, + + /// Radius is the vector (a, b) where the width of the Ellipse is 2a and the height is 2b + pub radius: Vec2, + pub fill: Color32, + pub stroke: Stroke, +} + +impl EllipseShape { + #[inline] + pub fn filled(center: Pos2, radius: Vec2, fill_color: impl Into) -> Self { + Self { + center, + radius, + fill: fill_color.into(), + stroke: Default::default(), + } + } + + #[inline] + pub fn stroke(center: Pos2, radius: Vec2, stroke: impl Into) -> Self { + Self { + center, + radius, + fill: Default::default(), + stroke: stroke.into(), + } + } + + /// The visual bounding rectangle (includes stroke width) + pub fn visual_bounding_rect(&self) -> Rect { + if self.fill == Color32::TRANSPARENT && self.stroke.is_empty() { + Rect::NOTHING + } else { + Rect::from_center_size( + self.center, + self.radius * 2.0 + Vec2::splat(self.stroke.width), + ) + } + } +} + +impl From for Shape { + #[inline(always)] + fn from(shape: EllipseShape) -> Self { + Self::Ellipse(shape) + } +} + +// ---------------------------------------------------------------------------- + /// A path which can be stroked and/or filled (if closed). #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] diff --git a/crates/epaint/src/shape_transform.rs b/crates/epaint/src/shape_transform.rs index c8edff1fcaf..7f4d335845f 100644 --- a/crates/epaint/src/shape_transform.rs +++ b/crates/epaint/src/shape_transform.rs @@ -20,6 +20,12 @@ pub fn adjust_colors(shape: &mut Shape, adjust_color: &impl Fn(&mut Color32)) { fill, stroke, }) + | Shape::Ellipse(EllipseShape { + center: _, + radius: _, + fill, + stroke, + }) | Shape::Path(PathShape { points: _, closed: _, diff --git a/crates/epaint/src/stats.rs b/crates/epaint/src/stats.rs index 56eaa24ac7a..3bb51e14170 100644 --- a/crates/epaint/src/stats.rs +++ b/crates/epaint/src/stats.rs @@ -201,6 +201,7 @@ impl PaintStats { } Shape::Noop | Shape::Circle { .. } + | Shape::Ellipse { .. } | Shape::LineSegment { .. } | Shape::Rect { .. } | Shape::CubicBezier(_) diff --git a/crates/epaint/src/tessellator.rs b/crates/epaint/src/tessellator.rs index c753b7374df..bf419e99b58 100644 --- a/crates/epaint/src/tessellator.rs +++ b/crates/epaint/src/tessellator.rs @@ -1215,6 +1215,9 @@ impl Tessellator { Shape::Circle(circle) => { self.tessellate_circle(circle, out); } + Shape::Ellipse(ellipse) => { + self.tessellate_ellipse(ellipse, out); + } Shape::Mesh(mesh) => { crate::profile_scope!("mesh"); @@ -1315,6 +1318,73 @@ impl Tessellator { .stroke_closed(self.feathering, stroke, out); } + /// Tessellate a single [`EllipseShape`] into a [`Mesh`]. + /// + /// * `shape`: the ellipse to tessellate. + /// * `out`: triangles are appended to this. + pub fn tessellate_ellipse(&mut self, shape: EllipseShape, out: &mut Mesh) { + let EllipseShape { + center, + radius, + fill, + stroke, + } = shape; + + if radius.x <= 0.0 || radius.y <= 0.0 { + return; + } + + if self.options.coarse_tessellation_culling + && !self + .clip_rect + .expand2(radius + Vec2::splat(stroke.width)) + .contains(center) + { + return; + } + + // Get the max pixel radius + let max_radius = (radius.max_elem() * self.pixels_per_point) as u32; + + // Ensure there is at least 8 points in each quarter of the ellipse + let num_points = u32::max(8, max_radius / 16); + + // Create an ease ratio based the ellipses a and b + let ratio = ((radius.y / radius.x) / 2.0).clamp(0.0, 1.0); + + // Generate points between the 0 to pi/2 + let quarter: Vec = (1..num_points) + .map(|i| { + let percent = i as f32 / num_points as f32; + + // Ease the percent value, concentrating points around tight bends + let eased = 2.0 * (percent - percent.powf(2.0)) * ratio + percent.powf(2.0); + + // Scale the ease to the quarter + let t = eased * std::f32::consts::FRAC_PI_2; + Vec2::new(radius.x * f32::cos(t), radius.y * f32::sin(t)) + }) + .collect(); + + // Build the ellipse from the 4 known vertices filling arcs between + // them by mirroring the points between 0 and pi/2 + let mut points = Vec::new(); + points.push(center + Vec2::new(radius.x, 0.0)); + points.extend(quarter.iter().map(|p| center + *p)); + points.push(center + Vec2::new(0.0, radius.y)); + points.extend(quarter.iter().rev().map(|p| center + Vec2::new(-p.x, p.y))); + points.push(center + Vec2::new(-radius.x, 0.0)); + points.extend(quarter.iter().map(|p| center - *p)); + points.push(center + Vec2::new(0.0, -radius.y)); + points.extend(quarter.iter().rev().map(|p| center + Vec2::new(p.x, -p.y))); + + self.scratchpad_path.clear(); + self.scratchpad_path.add_line_loop(&points); + self.scratchpad_path.fill(self.feathering, fill, out); + self.scratchpad_path + .stroke_closed(self.feathering, stroke, out); + } + /// Tessellate a single [`Mesh`] into a [`Mesh`]. /// /// * `mesh`: the mesh to tessellate. @@ -1776,7 +1846,7 @@ impl Tessellator { Shape::Path(path_shape) => 32 < path_shape.points.len(), - Shape::QuadraticBezier(_) | Shape::CubicBezier(_) => true, + Shape::QuadraticBezier(_) | Shape::CubicBezier(_) | Shape::Ellipse(_) => true, Shape::Noop | Shape::Text(_) From 8ca270e78ef00d8413143742ae5aff2d777e6823 Mon Sep 17 00:00:00 2001 From: zaaarf Date: Wed, 20 Mar 2024 13:54:14 +0100 Subject: [PATCH 019/134] Option to change date picker format (#4180) Simply allows date picker buttons to show other formats than `%Y-%m-%d`, while keeping that as default to not break compatibility. I'm not that experienced with Rust, so I was unsure whether you'd prefer `&'a str` rather than a `String`, let me know if I should change that. --------- Co-authored-by: Emil Ernerfeldt --- crates/egui_extras/src/datepicker/button.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/crates/egui_extras/src/datepicker/button.rs b/crates/egui_extras/src/datepicker/button.rs index 5aa6eaeadfb..a81747ff55a 100644 --- a/crates/egui_extras/src/datepicker/button.rs +++ b/crates/egui_extras/src/datepicker/button.rs @@ -16,6 +16,7 @@ pub struct DatePickerButton<'a> { calendar: bool, calendar_week: bool, show_icon: bool, + format: String, } impl<'a> DatePickerButton<'a> { @@ -28,6 +29,7 @@ impl<'a> DatePickerButton<'a> { calendar: true, calendar_week: true, show_icon: true, + format: "%Y-%m-%d".to_owned(), } } @@ -73,6 +75,14 @@ impl<'a> DatePickerButton<'a> { self.show_icon = show_icon; self } + + /// Change the format shown on the button. (Default: %Y-%m-%d) + /// See [`chrono::format::strftime`] for valid formats. + #[inline] + pub fn format(mut self, format: impl Into) -> Self { + self.format = format.into(); + self + } } impl<'a> Widget for DatePickerButton<'a> { @@ -83,9 +93,9 @@ impl<'a> Widget for DatePickerButton<'a> { .unwrap_or_default(); let mut text = if self.show_icon { - RichText::new(format!("{} 📆", self.selection.format("%Y-%m-%d"))) + RichText::new(format!("{} 📆", self.selection.format(&self.format))) } else { - RichText::new(format!("{}", self.selection.format("%Y-%m-%d"))) + RichText::new(format!("{}", self.selection.format(&self.format))) }; let visuals = ui.visuals().widgets.open; if button_state.picker_visible { From 861a1b622587e280dfb9ddf329a5e80b2a39ff3b Mon Sep 17 00:00:00 2001 From: rustbasic <127506429+rustbasic@users.noreply.github.com> Date: Thu, 21 Mar 2024 18:48:40 +0900 Subject: [PATCH 020/134] Adjustable Slider rail height (#4092) Adjustable Slider rail height ![explain20240312-2](https://github.com/emilk/egui/assets/127506429/d5edfe10-8191-44ed-8567-d9d2127ce4b8) --------- Co-authored-by: Emil Ernerfeldt --- crates/egui/src/style.rs | 9 +++++++++ crates/egui/src/widgets/slider.rs | 14 ++++---------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index a5b19e14d9d..ac37f6ebe14 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -281,6 +281,9 @@ pub struct Spacing { /// Default width of a [`Slider`]. pub slider_width: f32, + /// Default rail height of a [`Slider`]. + pub slider_rail_height: f32, + /// Default (minimum) width of a [`ComboBox`](crate::ComboBox). pub combo_width: f32, @@ -1224,6 +1227,7 @@ impl Default for Spacing { indent: 18.0, // match checkbox/radio-button with `button_padding.x + icon_width + icon_spacing` interact_size: vec2(40.0, 18.0), slider_width: 100.0, + slider_rail_height: 8.0, combo_width: 100.0, text_edit_width: 280.0, icon_width: 14.0, @@ -1573,6 +1577,7 @@ impl Spacing { indent, interact_size, slider_width, + slider_rail_height, combo_width, text_edit_width, icon_width, @@ -1601,6 +1606,10 @@ impl Spacing { ui.add(DragValue::new(slider_width).clamp_range(0.0..=1000.0)); ui.label("Slider width"); }); + ui.horizontal(|ui| { + ui.add(DragValue::new(slider_rail_height).clamp_range(0.0..=50.0)); + ui.label("Slider rail height"); + }); ui.horizontal(|ui| { ui.add(DragValue::new(combo_width).clamp_range(0.0..=1000.0)); ui.label("ComboBox width"); diff --git a/crates/egui/src/widgets/slider.rs b/crates/egui/src/widgets/slider.rs index 431dd6e55c2..c5c60b92610 100644 --- a/crates/egui/src/widgets/slider.rs +++ b/crates/egui/src/widgets/slider.rs @@ -680,11 +680,12 @@ impl<'a> Slider<'a> { if ui.is_rect_visible(response.rect) { let value = self.get_value(); - let rail_radius = ui.painter().round_to_pixel(self.rail_radius_limit(rect)); - let rail_rect = self.rail_rect(rect, rail_radius); - let visuals = ui.style().interact(response); let widget_visuals = &ui.visuals().widgets; + let spacing = &ui.style().spacing; + + let rail_radius = (spacing.slider_rail_height / 2.0).at_least(0.0); + let rail_rect = self.rail_rect(rect, rail_radius); ui.painter().rect_filled( rail_rect, @@ -800,13 +801,6 @@ impl<'a> Slider<'a> { limit / 2.5 } - fn rail_radius_limit(&self, rect: &Rect) -> f32 { - match self.orientation { - SliderOrientation::Horizontal => (rect.height() / 4.0).at_least(2.0), - SliderOrientation::Vertical => (rect.width() / 4.0).at_least(2.0), - } - } - fn value_ui(&mut self, ui: &mut Ui, position_range: Rangef) -> Response { // If [`DragValue`] is controlled from the keyboard and `step` is defined, set speed to `step` let change = ui.input(|input| { From 365d035b20da72c560f0861413673d76762dd98d Mon Sep 17 00:00:00 2001 From: hiyosilver <130156584+hiyosilver@users.noreply.github.com> Date: Thu, 21 Mar 2024 11:09:19 +0100 Subject: [PATCH 021/134] Added ability to disable highlighting of weekend days in DatePickerPopup. (#4151) I added a handfull of lines to allow for the red highlight on weekends in `DatePickerPopup` to be disabled. I tried to follow the rules, but I've never done any kind of PR before, and I'm also not sure if this is at all the kind of thing that is wanted at the moment. If it is not, I'm happy to just have this removed. Just a tiny little addition I would find useful. --------- Co-authored-by: Emil Ernerfeldt --- crates/egui_extras/src/datepicker/button.rs | 10 ++++++++++ crates/egui_extras/src/datepicker/popup.rs | 6 ++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/crates/egui_extras/src/datepicker/button.rs b/crates/egui_extras/src/datepicker/button.rs index a81747ff55a..839adebfc47 100644 --- a/crates/egui_extras/src/datepicker/button.rs +++ b/crates/egui_extras/src/datepicker/button.rs @@ -17,6 +17,7 @@ pub struct DatePickerButton<'a> { calendar_week: bool, show_icon: bool, format: String, + highlight_weekends: bool, } impl<'a> DatePickerButton<'a> { @@ -30,6 +31,7 @@ impl<'a> DatePickerButton<'a> { calendar_week: true, show_icon: true, format: "%Y-%m-%d".to_owned(), + highlight_weekends: true, } } @@ -83,6 +85,13 @@ impl<'a> DatePickerButton<'a> { self.format = format.into(); self } + + /// Highlight weekend days. (Default: true) + #[inline] + pub fn highlight_weekends(mut self, highlight_weekends: bool) -> Self { + self.highlight_weekends = highlight_weekends; + self + } } impl<'a> Widget for DatePickerButton<'a> { @@ -148,6 +157,7 @@ impl<'a> Widget for DatePickerButton<'a> { arrows: self.arrows, calendar: self.calendar, calendar_week: self.calendar_week, + highlight_weekends: self.highlight_weekends, } .draw(ui) }) diff --git a/crates/egui_extras/src/datepicker/popup.rs b/crates/egui_extras/src/datepicker/popup.rs index a787d3c55ba..c5bd41d59f5 100644 --- a/crates/egui_extras/src/datepicker/popup.rs +++ b/crates/egui_extras/src/datepicker/popup.rs @@ -33,6 +33,7 @@ pub(crate) struct DatePickerPopup<'a> { pub arrows: bool, pub calendar: bool, pub calendar_week: bool, + pub highlight_weekends: bool, } impl<'a> DatePickerPopup<'a> { @@ -304,8 +305,9 @@ impl<'a> DatePickerPopup<'a> { && popup_state.day == day.day() { ui.visuals().selection.bg_fill - } else if day.weekday() == Weekday::Sat - || day.weekday() == Weekday::Sun + } else if (day.weekday() == Weekday::Sat + || day.weekday() == Weekday::Sun) + && self.highlight_weekends { if ui.visuals().dark_mode { Color32::DARK_RED From 2c9c5f0dea48581f165d1bb644d54586d95cfc03 Mon Sep 17 00:00:00 2001 From: Seth Rider <2758832+psethwick@users.noreply.github.com> Date: Thu, 21 Mar 2024 11:07:20 +0000 Subject: [PATCH 022/134] Add x11 window type settings to viewport builder (#4175) Not sure about the api, currently I've mapped the whole XWindowType enum, but maybe there's something more sensible to do? * Closes --------- Co-authored-by: Emil Ernerfeldt --- crates/egui-winit/src/lib.rs | 27 +++++++++++++ crates/egui/src/viewport.rs | 73 ++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+) diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index b6d02cdf12c..418c39ece32 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -1540,6 +1540,9 @@ pub fn create_winit_window_builder( // wayland: app_id: _app_id, + // x11 + window_type: _window_type, + mouse_passthrough: _, // handled in `apply_viewport_builder_to_window` } = viewport_builder; @@ -1612,6 +1615,30 @@ pub fn create_winit_window_builder( window_builder = window_builder.with_name(app_id, ""); } + #[cfg(all(feature = "x11", target_os = "linux"))] + { + if let Some(window_type) = _window_type { + use winit::platform::x11::WindowBuilderExtX11 as _; + use winit::platform::x11::XWindowType; + window_builder = window_builder.with_x11_window_type(vec![match window_type { + egui::X11WindowType::Normal => XWindowType::Normal, + egui::X11WindowType::Utility => XWindowType::Utility, + egui::X11WindowType::Dock => XWindowType::Dock, + egui::X11WindowType::Desktop => XWindowType::Desktop, + egui::X11WindowType::Toolbar => XWindowType::Toolbar, + egui::X11WindowType::Menu => XWindowType::Menu, + egui::X11WindowType::Splash => XWindowType::Splash, + egui::X11WindowType::Dialog => XWindowType::Dialog, + egui::X11WindowType::DropdownMenu => XWindowType::DropdownMenu, + egui::X11WindowType::PopupMenu => XWindowType::PopupMenu, + egui::X11WindowType::Tooltip => XWindowType::Tooltip, + egui::X11WindowType::Notification => XWindowType::Notification, + egui::X11WindowType::Combo => XWindowType::Combo, + egui::X11WindowType::Dnd => XWindowType::Dnd, + }]); + } + } + #[cfg(target_os = "windows")] { use winit::platform::windows::WindowBuilderExtWindows as _; diff --git a/crates/egui/src/viewport.rs b/crates/egui/src/viewport.rs index 4e88eab4b39..aed8d35165e 100644 --- a/crates/egui/src/viewport.rs +++ b/crates/egui/src/viewport.rs @@ -301,6 +301,9 @@ pub struct ViewportBuilder { pub window_level: Option, pub mouse_passthrough: Option, + + // X11 + pub window_type: Option, } impl ViewportBuilder { @@ -583,6 +586,15 @@ impl ViewportBuilder { self } + /// ### On X11 + /// This sets the window type. + /// Maps directly to [`_NET_WM_WINDOW_TYPE`](https://specifications.freedesktop.org/wm-spec/wm-spec-1.5.html). + #[inline] + pub fn with_window_type(mut self, value: X11WindowType) -> Self { + self.window_type = Some(value); + self + } + /// Update this `ViewportBuilder` with a delta, /// returning a list of commands and a bool indicating if the window needs to be recreated. #[must_use] @@ -613,6 +625,7 @@ impl ViewportBuilder { window_level: new_window_level, mouse_passthrough: new_mouse_passthrough, taskbar: new_taskbar, + window_type: new_window_type, } = new_vp_builder; let mut commands = Vec::new(); @@ -786,6 +799,11 @@ impl ViewportBuilder { recreate_window = true; } + if new_window_type.is_some() && self.window_type != new_window_type { + self.window_type = new_window_type; + recreate_window = true; + } + (commands, recreate_window) } } @@ -799,6 +817,61 @@ pub enum WindowLevel { AlwaysOnTop, } +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub enum X11WindowType { + /// This is a normal, top-level window. + #[default] + Normal, + + /// A desktop feature. This can include a single window containing desktop icons with the same dimensions as the + /// screen, allowing the desktop environment to have full control of the desktop, without the need for proxying + /// root window clicks. + Desktop, + + /// A dock or panel feature. Typically a Window Manager would keep such windows on top of all other windows. + Dock, + + /// Toolbar windows. "Torn off" from the main application. + Toolbar, + + /// Pinnable menu windows. "Torn off" from the main application. + Menu, + + /// A small persistent utility window, such as a palette or toolbox. + Utility, + + /// The window is a splash screen displayed as an application is starting up. + Splash, + + /// This is a dialog window. + Dialog, + + /// A dropdown menu that usually appears when the user clicks on an item in a menu bar. + /// This property is typically used on override-redirect windows. + DropdownMenu, + + /// A popup menu that usually appears when the user right clicks on an object. + /// This property is typically used on override-redirect windows. + PopupMenu, + + /// A tooltip window. Usually used to show additional information when hovering over an object with the cursor. + /// This property is typically used on override-redirect windows. + Tooltip, + + /// The window is a notification. + /// This property is typically used on override-redirect windows. + Notification, + + /// This should be used on the windows that are popped up by combo boxes. + /// This property is typically used on override-redirect windows. + Combo, + + /// This indicates the the window is being dragged. + /// This property is typically used on override-redirect windows. + Dnd, +} + #[derive(Clone, Copy, Default, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub enum IMEPurpose { From 7d3d7ce0cab1b82c11c9302e01c0770c8e1986db Mon Sep 17 00:00:00 2001 From: rustbasic <127506429+rustbasic@users.noreply.github.com> Date: Thu, 21 Mar 2024 20:10:02 +0900 Subject: [PATCH 023/134] typos : intersects_ray() (#4201) typos : intersects_ray() --- crates/egui/src/menu.rs | 2 +- crates/emath/src/rect.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/egui/src/menu.rs b/crates/egui/src/menu.rs index 6059236787c..36d12497de6 100644 --- a/crates/egui/src/menu.rs +++ b/crates/egui/src/menu.rs @@ -641,7 +641,7 @@ impl MenuState { if let Some(sub_menu) = self.current_submenu() { if let Some(pos) = pointer.hover_pos() { let rect = sub_menu.read().rect; - return rect.intesects_ray(pos, pointer.velocity().normalized()); + return rect.intersects_ray(pos, pointer.velocity().normalized()); } } false diff --git a/crates/emath/src/rect.rs b/crates/emath/src/rect.rs index 812d76c0d14..72331dae1fc 100644 --- a/crates/emath/src/rect.rs +++ b/crates/emath/src/rect.rs @@ -607,7 +607,7 @@ impl Rect { impl Rect { /// Does this Rect intersect the given ray (where `d` is normalized)? - pub fn intesects_ray(&self, o: Pos2, d: Vec2) -> bool { + pub fn intersects_ray(&self, o: Pos2, d: Vec2) -> bool { let mut tmin = -f32::INFINITY; let mut tmax = f32::INFINITY; From e4f209ec50692db9f551d3ddf81ce5f8046cf7f7 Mon Sep 17 00:00:00 2001 From: Zeenobit Date: Thu, 21 Mar 2024 07:13:32 -0400 Subject: [PATCH 024/134] Expose state override for `HeaderResponse` (#4200) I'm trying to create some custom collapsing headers that add additional buttons inside the header itself. Because of this, I load the `CollapsingState` in my special widget manually. But because `HeaderResponse` owns the `Ui` and the `CollapsingState`, there is no way for me to open/close the collapsing header based on response of the inner widget. Initially, I considered just exposing `state` member of `HeaderResponse`, but that exposes too much of the API at the wrong time, in my opinion. So instead I found it'd be safer to just expose the open/close API to the response itself, and that's what this PR does. --- crates/egui/src/containers/collapsing_header.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/crates/egui/src/containers/collapsing_header.rs b/crates/egui/src/containers/collapsing_header.rs index cb48ca1ede9..6fb7662af22 100644 --- a/crates/egui/src/containers/collapsing_header.rs +++ b/crates/egui/src/containers/collapsing_header.rs @@ -272,6 +272,18 @@ pub struct HeaderResponse<'ui, HeaderRet> { } impl<'ui, HeaderRet> HeaderResponse<'ui, HeaderRet> { + pub fn is_open(&self) -> bool { + self.state.is_open() + } + + pub fn set_open(&mut self, open: bool) { + self.state.set_open(open); + } + + pub fn toggle(&mut self) { + self.state.toggle(self.ui); + } + /// Returns the response of the collapsing button, the custom header, and the custom body. pub fn body( mut self, From 0299663cdd1da6b716bb62083bd1e212fb7e4abe Mon Sep 17 00:00:00 2001 From: dwuertz Date: Thu, 21 Mar 2024 12:27:18 +0100 Subject: [PATCH 025/134] Expose `PlotGeometry` in public API (#4193) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Expose `egui_plot::items::values::PlotGeometry` in public API so that `PlotItem`, which is already public, can actually be implemented by applications. Fixes #3464. --------- Co-authored-by: Dominique Würtz --- crates/egui_plot/src/items/mod.rs | 4 ++-- crates/egui_plot/src/lib.rs | 3 ++- crates/egui_plot/src/plot_ui.rs | 5 +++++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/crates/egui_plot/src/items/mod.rs b/crates/egui_plot/src/items/mod.rs index 122189d2264..3df3b798dd3 100644 --- a/crates/egui_plot/src/items/mod.rs +++ b/crates/egui_plot/src/items/mod.rs @@ -9,11 +9,11 @@ use crate::*; use super::{Cursor, LabelFormatter, PlotBounds, PlotTransform}; use rect_elem::*; -use values::{ClosestElem, PlotGeometry}; +use values::ClosestElem; pub use bar::Bar; pub use box_elem::{BoxElem, BoxSpread}; -pub use values::{LineStyle, MarkerShape, Orientation, PlotPoint, PlotPoints}; +pub use values::{LineStyle, MarkerShape, Orientation, PlotGeometry, PlotPoint, PlotPoints}; mod bar; mod box_elem; diff --git a/crates/egui_plot/src/lib.rs b/crates/egui_plot/src/lib.rs index 4238c7b3258..ed133d7edb6 100644 --- a/crates/egui_plot/src/lib.rs +++ b/crates/egui_plot/src/lib.rs @@ -23,7 +23,8 @@ pub use crate::{ axis::{Axis, AxisHints, HPlacement, Placement, VPlacement}, items::{ Arrows, Bar, BarChart, BoxElem, BoxPlot, BoxSpread, HLine, Line, LineStyle, MarkerShape, - Orientation, PlotImage, PlotItem, PlotPoint, PlotPoints, Points, Polygon, Text, VLine, + Orientation, PlotGeometry, PlotImage, PlotItem, PlotPoint, PlotPoints, Points, Polygon, + Text, VLine, }, legend::{Corner, Legend}, memory::PlotMemory, diff --git a/crates/egui_plot/src/plot_ui.rs b/crates/egui_plot/src/plot_ui.rs index fb126298df5..c4a39a6d728 100644 --- a/crates/egui_plot/src/plot_ui.rs +++ b/crates/egui_plot/src/plot_ui.rs @@ -116,6 +116,11 @@ impl PlotUi { self.last_plot_transform.value_from_position(position) } + /// Add an arbitrary item. + pub fn add(&mut self, item: impl PlotItem + 'static) { + self.items.push(Box::new(item)); + } + /// Add a data line. pub fn line(&mut self, mut line: Line) { if line.series.is_empty() { From 769199648db30dadbec21cd1845bc325c5c91024 Mon Sep 17 00:00:00 2001 From: rustbasic <127506429+rustbasic@users.noreply.github.com> Date: Thu, 21 Mar 2024 21:47:25 +0900 Subject: [PATCH 026/134] Prevent egui::Window from becoming larger than Viewport (#4199) * Closes #3987 * Closes #3988 There is a need to prevent egui::Window from becoming larger than the Viewport. --------- Co-authored-by: Emil Ernerfeldt --- crates/egui/src/containers/window.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index 698033cf9c4..af6f6b16c31 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -431,6 +431,16 @@ impl<'open> Window<'open> { (0.0, 0.0) }; + { + // Prevent window from becoming larger than the constraint rect and/or screen rect. + let screen_rect = ctx.screen_rect(); + let max_rect = area.constrain_rect().unwrap_or(screen_rect); + let max_width = max_rect.width(); + let max_height = max_rect.height() - title_bar_height; + resize.max_size.x = resize.max_size.x.min(max_width); + resize.max_size.y = resize.max_size.y.min(max_height); + } + // First check for resize to avoid frame delay: let last_frame_outer_rect = area.state().rect(); let resize_interaction = From 21d045e8eff2c69e63b39004f74fd0e65bc2caaf Mon Sep 17 00:00:00 2001 From: rustbasic <127506429+rustbasic@users.noreply.github.com> Date: Fri, 22 Mar 2024 20:27:08 +0900 Subject: [PATCH 027/134] Fix: IME bug when typing Chinese characters (#4210) * Closes #4209 Fix: IME bug when typing Chinese characters --- crates/egui/src/widgets/text_edit/builder.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index 12077b93292..3c4f6c51e7c 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -953,7 +953,10 @@ fn events( if prediction != "\n" && prediction != "\r" { state.has_ime = false; let mut ccursor; - if !prediction.is_empty() && cursor_range == state.ime_cursor_range { + if !prediction.is_empty() + && cursor_range.secondary.ccursor.index + == state.ime_cursor_range.secondary.ccursor.index + { ccursor = text.delete_selected(&cursor_range); text.insert_text_at(&mut ccursor, prediction, char_limit); } else { From b7ec3fa1e35789995248374e8d2bbca8dccdc6e3 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 22 Mar 2024 12:33:17 +0100 Subject: [PATCH 028/134] Fix crash in `wants_keyboard_input` --- crates/egui/src/memory.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/crates/egui/src/memory.rs b/crates/egui/src/memory.rs index 3ea3a19fe28..dd5e28b1bd6 100644 --- a/crates/egui/src/memory.rs +++ b/crates/egui/src/memory.rs @@ -639,7 +639,7 @@ impl Memory { } pub(crate) fn had_focus_last_frame(&self, id: Id) -> bool { - self.focus().id_previous_frame == Some(id) + self.focus().and_then(|f| f.id_previous_frame) == Some(id) } /// True if the given widget had keyboard focus last frame, but not this one. @@ -665,7 +665,7 @@ impl Memory { /// Which widget has keyboard focus? pub fn focused(&self) -> Option { - self.focus().focused() + self.focus().and_then(|f| f.focused()) } /// Set an event filter for a widget. @@ -797,10 +797,8 @@ impl Memory { self.interactions.entry(self.viewport_id).or_default() } - pub(crate) fn focus(&self) -> &Focus { - self.focus - .get(&self.viewport_id) - .expect("Failed to get focus") + pub(crate) fn focus(&self) -> Option<&Focus> { + self.focus.get(&self.viewport_id) } pub(crate) fn focus_mut(&mut self) -> &mut Focus { From 20b0637d42892b87fb8411b39c5c256fd32d7ee3 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 22 Mar 2024 12:33:35 +0100 Subject: [PATCH 029/134] Some clippy fixes --- crates/eframe/src/native/file_storage.rs | 5 +++-- crates/egui/src/memory.rs | 8 ++------ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/crates/eframe/src/native/file_storage.rs b/crates/eframe/src/native/file_storage.rs index 79c0c97a071..51c668abdeb 100644 --- a/crates/eframe/src/native/file_storage.rs +++ b/crates/eframe/src/native/file_storage.rs @@ -99,11 +99,12 @@ impl crate::Storage for FileStorage { join_handle.join().ok(); } - match std::thread::Builder::new() + let result = std::thread::Builder::new() .name("eframe_persist".to_owned()) .spawn(move || { save_to_disk(&file_path, &kv); - }) { + }); + match result { Ok(join_handle) => { self.last_save_join_handle = Some(join_handle); } diff --git a/crates/egui/src/memory.rs b/crates/egui/src/memory.rs index dd5e28b1bd6..068347b5697 100644 --- a/crates/egui/src/memory.rs +++ b/crates/egui/src/memory.rs @@ -521,9 +521,7 @@ impl Focus { } } - let Some(current_focused) = self.focused_widget else { - return None; - }; + let current_focused = self.focused_widget?; // In what direction we are looking for the next widget. let search_direction = match self.focus_direction { @@ -546,9 +544,7 @@ impl Focus { } }); - let Some(current_rect) = self.focus_widgets_cache.get(¤t_focused.id) else { - return None; - }; + let current_rect = self.focus_widgets_cache.get(¤t_focused.id)?; let mut best_score = std::f32::INFINITY; let mut best_id = None; From 7f8aae4103097ebe245b5199e6ecd79fb3a874a3 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 25 Mar 2024 09:00:12 +0100 Subject: [PATCH 030/134] Don't show URLs when hovering hyperlinks (#4218) Can be changed with `Style::url_in_tooltip` --- crates/egui/src/style.rs | 7 +++++++ crates/egui/src/widgets/hyperlink.rs | 7 ++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index ac37f6ebe14..54e6ab26feb 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -213,6 +213,9 @@ pub struct Style { /// This only affects a few egui widgets. pub explanation_tooltips: bool, + /// Show the URL of hyperlinks in a tooltip when hovered. + pub url_in_tooltip: bool, + /// If true and scrolling is enabled for only one direction, allow horizontal scrolling without pressing shift pub always_scroll_the_only_direction: bool, } @@ -1212,6 +1215,7 @@ impl Default for Style { #[cfg(debug_assertions)] debug: Default::default(), explanation_tooltips: false, + url_in_tooltip: false, always_scroll_the_only_direction: false, } } @@ -1474,6 +1478,7 @@ impl Style { #[cfg(debug_assertions)] debug, explanation_tooltips, + url_in_tooltip, always_scroll_the_only_direction, } = self; @@ -1544,6 +1549,8 @@ impl Style { "Show explanatory text when hovering DragValue:s and other egui widgets", ); + ui.checkbox(url_in_tooltip, "Show url when hovering links"); + ui.checkbox(always_scroll_the_only_direction, "Always scroll the only enabled direction") .on_hover_text( "If scrolling is enabled for only one direction, allow horizontal scrolling without pressing shift", diff --git a/crates/egui/src/widgets/hyperlink.rs b/crates/egui/src/widgets/hyperlink.rs index f4a508e64f7..ad3b76eeddd 100644 --- a/crates/egui/src/widgets/hyperlink.rs +++ b/crates/egui/src/widgets/hyperlink.rs @@ -136,6 +136,11 @@ impl Widget for Hyperlink { new_tab: true, }); } - response.on_hover_text(url) + + if ui.style().url_in_tooltip { + response.on_hover_text(url) + } else { + response + } } } From d410bc5b9a392f26213fbb4e5aadb3d995d45cc0 Mon Sep 17 00:00:00 2001 From: rustbasic <127506429+rustbasic@users.noreply.github.com> Date: Mon, 25 Mar 2024 20:40:49 +0900 Subject: [PATCH 031/134] Fix `example/test_viewports` title change (#4221) fix : `example/test_viewports` title Change accurately --------- Co-authored-by: Emil Ernerfeldt --- examples/test_viewports/src/main.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/test_viewports/src/main.rs b/examples/test_viewports/src/main.rs index b407ff5a12c..69be9177127 100644 --- a/examples/test_viewports/src/main.rs +++ b/examples/test_viewports/src/main.rs @@ -192,8 +192,11 @@ fn generic_child_ui(ui: &mut egui::Ui, vp_state: &mut ViewportState) { ui.horizontal(|ui| { ui.label("Title:"); if ui.text_edit_singleline(&mut vp_state.title).changed() { - // Title changes happen at the parent level: - ui.ctx().request_repaint_of(ui.ctx().parent_viewport_id()); + // Title changes + ui.ctx().send_viewport_cmd_to( + vp_state.id, + egui::ViewportCommand::Title(vp_state.title.clone()), + ); } }); From 287a550b906018213008fc1d5972b7975079a30b Mon Sep 17 00:00:00 2001 From: Colin Kinloch Date: Mon, 25 Mar 2024 12:11:39 +0000 Subject: [PATCH 032/134] egui demo: Add drop down to resize the window (#4197) An alternative to the "Phone Size" button useful for testing `ViewportCommand::InnerSize`. I created this to make it easy to debug https://github.com/emilk/egui/issues/4196 there are more details there. --- crates/egui_demo_app/src/backend_panel.rs | 28 +++++++++++++++++------ 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/crates/egui_demo_app/src/backend_panel.rs b/crates/egui_demo_app/src/backend_panel.rs index 2e2f0f0a71c..51225587134 100644 --- a/crates/egui_demo_app/src/backend_panel.rs +++ b/crates/egui_demo_app/src/backend_panel.rs @@ -296,14 +296,28 @@ fn integration_ui(ui: &mut egui::Ui, _frame: &mut eframe::Frame) { } } - if ui - .button("📱 Phone Size") - .on_hover_text("Resize the window to be small like a phone.") - .clicked() - { - // let size = egui::vec2(375.0, 812.0); // iPhone 12 mini - let size = egui::vec2(375.0, 667.0); // iPhone SE 2nd gen + let mut size = None; + egui::ComboBox::from_id_source("viewport-size-combo") + .selected_text("Resize to...") + .show_ui(ui, |ui| { + ui.selectable_value( + &mut size, + Some(egui::vec2(375.0, 667.0)), + "📱 iPhone SE 2nd Gen", + ); + ui.selectable_value( + &mut size, + Some(egui::vec2(1280.0, 720.0)), + "🖥 Desktop 720p", + ); + ui.selectable_value( + &mut size, + Some(egui::vec2(1920.0, 1080.0)), + "🖥 Desktop 1080p", + ); + }); + if let Some(size) = size { ui.ctx() .send_viewport_cmd(egui::ViewportCommand::InnerSize(size)); ui.ctx() From ab6c3f91611268e0b86c03d7b7f3a51dec7f7692 Mon Sep 17 00:00:00 2001 From: "/lemon.sh" Date: Mon, 25 Mar 2024 13:18:08 +0100 Subject: [PATCH 033/134] Add an API for customizing the return key in TextEdit (#4085) This PR allows customizing the return key in the TextEdit widget. Right now, it's hard-coded to the Enter key, which is problematic in cases when you want to use the Enter key for something else, and insert the newline in a different way instead. --- crates/egui/src/widgets/text_edit/builder.rs | 24 +++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index 3c4f6c51e7c..bed9b6a6d29 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -80,6 +80,7 @@ pub struct TextEdit<'t> { align: Align2, clip_text: bool, char_limit: usize, + return_key: KeyboardShortcut, } impl<'t> WidgetWithState for TextEdit<'t> { @@ -107,7 +108,7 @@ impl<'t> TextEdit<'t> { } } - /// A [`TextEdit`] for multiple lines. Pressing enter key will create a new line. + /// A [`TextEdit`] for multiple lines. Pressing enter key will create a new line by default (can be changed with [`return_key`](TextEdit::return_key)). pub fn multiline(text: &'t mut dyn TextBuffer) -> Self { Self { text, @@ -136,6 +137,7 @@ impl<'t> TextEdit<'t> { align: Align2::LEFT_TOP, clip_text: false, char_limit: usize::MAX, + return_key: KeyboardShortcut::new(Modifiers::NONE, Key::Enter), } } @@ -348,6 +350,16 @@ impl<'t> TextEdit<'t> { self.min_size = min_size; self } + + /// Set the return key combination. + /// + /// This combination will cause a newline on multiline, + /// whereas on singleline it will cause the widget to lose focus. + #[inline] + pub fn return_key(mut self, return_key: KeyboardShortcut) -> Self { + self.return_key = return_key; + self + } } // ---------------------------------------------------------------------------- @@ -453,6 +465,7 @@ impl<'t> TextEdit<'t> { align, clip_text, char_limit, + return_key, } = self; let text_color = text_color @@ -594,6 +607,7 @@ impl<'t> TextEdit<'t> { default_cursor_range, char_limit, event_filter, + return_key, ); if changed { @@ -785,6 +799,7 @@ fn events( default_cursor_range: CursorRange, char_limit: usize, event_filter: EventFilter, + return_key: KeyboardShortcut, ) -> (bool, CursorRange) { let os = ui.ctx().os(); @@ -867,10 +882,13 @@ fn events( Some(CCursorRange::one(ccursor)) } Event::Key { - key: Key::Enter, + key, pressed: true, + modifiers, .. - } => { + } if *key == return_key.logical_key + && modifiers.matches_logically(return_key.modifiers) => + { if multiline { let mut ccursor = text.delete_selected(&cursor_range); text.insert_text_at(&mut ccursor, "\n", char_limit); From cf8c37c71e249fb131a3de955d975bae53f0862b Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 26 Mar 2024 08:43:38 +0100 Subject: [PATCH 034/134] Add some distance between parent menu and submenu (#4230) Before: Screenshot 2024-03-26 at 08 37 16 After: Screenshot 2024-03-26 at 08 36 12 --- crates/egui/src/menu.rs | 28 ++++++++++++++++------------ crates/egui/src/style.rs | 10 ++++++++++ 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/crates/egui/src/menu.rs b/crates/egui/src/menu.rs index 36d12497de6..607b9495a77 100644 --- a/crates/egui/src/menu.rs +++ b/crates/egui/src/menu.rs @@ -151,20 +151,22 @@ pub(crate) fn menu_ui<'c, R>( .constrain_to(ctx.screen_rect()) .interactable(true); - area.show(ctx, |ui| { + let area_response = area.show(ctx, |ui| { set_menu_style(ui.style_mut()); - let frame = Frame::menu(ui.style()).show(ui, |ui| { - ui.set_max_width(ui.spacing().menu_width); - ui.set_menu_state(Some(menu_state_arc.clone())); - ui.with_layout(Layout::top_down_justified(Align::LEFT), add_contents) - .inner - }); + Frame::menu(ui.style()) + .show(ui, |ui| { + ui.set_max_width(ui.spacing().menu_width); + ui.set_menu_state(Some(menu_state_arc.clone())); + ui.with_layout(Layout::top_down_justified(Align::LEFT), add_contents) + .inner + }) + .inner + }); - menu_state_arc.write().rect = frame.response.rect; + menu_state_arc.write().rect = area_response.response.rect; - frame.inner - }) + area_response } /// Build a top level menu with a button. @@ -549,7 +551,8 @@ pub(crate) struct MenuState { /// The opened sub-menu and its [`Id`] sub_menu: Option<(Id, Arc>)>, - /// Bounding box of this menu (without the sub-menu) + /// Bounding box of this menu (without the sub-menu), + /// including the frame and everything. pub rect: Rect, /// Used to check if any menu in the tree wants to close @@ -620,7 +623,8 @@ impl MenuState { // ensure to repaint once even when pointer is not moving ui.ctx().request_repaint(); } else if !open && button.hovered() { - let pos = button.rect.right_top(); + let mut pos = button.rect.right_top(); + pos.x = self.rect.right() + ui.spacing().menu_spacing; self.open_submenu(sub_id, pos); } else if open && ui.interact_bg(Sense::hover()).contains_pointer() diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index 54e6ab26feb..651ad26930c 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -311,6 +311,9 @@ pub struct Spacing { /// The default width of a menu. pub menu_width: f32, + /// Horizontal distance between a menu and a submenu. + pub menu_spacing: f32, + /// End indented regions with a horizontal line pub indent_ends_with_horizontal_line: bool, @@ -1239,6 +1242,7 @@ impl Default for Spacing { icon_spacing: 4.0, tooltip_width: 600.0, menu_width: 150.0, + menu_spacing: 3.0, combo_height: 200.0, scroll: Default::default(), indent_ends_with_horizontal_line: false, @@ -1592,6 +1596,7 @@ impl Spacing { icon_spacing, tooltip_width, menu_width, + menu_spacing, indent_ends_with_horizontal_line, combo_height, scroll, @@ -1659,6 +1664,11 @@ impl Spacing { ui.label("Default width of a menu"); }); + ui.horizontal(|ui| { + ui.add(DragValue::new(menu_spacing).clamp_range(0.0..=10.0)); + ui.label("Horizontal spacing between menus"); + }); + ui.checkbox( indent_ends_with_horizontal_line, "End indented regions with a horizontal separator", From 1634554032d2f32a1da2b518923a8bd43b4b2aa3 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 26 Mar 2024 09:05:29 +0100 Subject: [PATCH 035/134] Add `Margin` to `epaint` (#4231) Moved from `egui` --- crates/egui/src/containers/frame.rs | 2 +- crates/egui/src/lib.rs | 6 +- crates/egui/src/style.rs | 213 +--------------------------- crates/epaint/src/lib.rs | 4 +- crates/epaint/src/margin.rs | 211 +++++++++++++++++++++++++++ 5 files changed, 220 insertions(+), 216 deletions(-) create mode 100644 crates/epaint/src/margin.rs diff --git a/crates/egui/src/containers/frame.rs b/crates/egui/src/containers/frame.rs index bd7f8eb7029..92113f4dd49 100644 --- a/crates/egui/src/containers/frame.rs +++ b/crates/egui/src/containers/frame.rs @@ -1,6 +1,6 @@ //! Frame container -use crate::{layers::ShapeIdx, style::Margin, *}; +use crate::{layers::ShapeIdx, *}; use epaint::*; /// Add a background, frame and/or margin to a rectangular background of a [`Ui`]. diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index 69328045387..e64668ec807 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -430,8 +430,8 @@ pub use epaint::{ mutex, text::{FontData, FontDefinitions, FontFamily, FontId, FontTweak}, textures::{TextureFilter, TextureOptions, TextureWrapMode, TexturesDelta}, - ClippedPrimitive, ColorImage, FontImage, ImageData, Mesh, PaintCallback, PaintCallbackInfo, - Rounding, Shape, Stroke, TextureHandle, TextureId, + ClippedPrimitive, ColorImage, FontImage, ImageData, Margin, Mesh, PaintCallback, + PaintCallbackInfo, Rounding, Shape, Stroke, TextureHandle, TextureId, }; pub mod text { @@ -463,7 +463,7 @@ pub use { painter::Painter, response::{InnerResponse, Response}, sense::Sense, - style::{FontSelection, Margin, Style, TextStyle, Visuals}, + style::{FontSelection, Style, TextStyle, Visuals}, text::{Galley, TextFormat}, ui::Ui, viewport::*, diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index 651ad26930c..c270add6362 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -7,7 +7,8 @@ use std::collections::BTreeMap; use epaint::{Rounding, Shadow, Stroke}; use crate::{ - ecolor::*, emath::*, ComboBox, CursorIcon, FontFamily, FontId, Response, RichText, WidgetText, + ecolor::*, emath::*, ComboBox, CursorIcon, FontFamily, FontId, Margin, Response, RichText, + WidgetText, }; // ---------------------------------------------------------------------------- @@ -613,216 +614,6 @@ impl ScrollStyle { // ---------------------------------------------------------------------------- -#[derive(Clone, Copy, Debug, Default, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -pub struct Margin { - pub left: f32, - pub right: f32, - pub top: f32, - pub bottom: f32, -} - -impl Margin { - pub const ZERO: Self = Self { - left: 0.0, - right: 0.0, - top: 0.0, - bottom: 0.0, - }; - - #[inline] - pub const fn same(margin: f32) -> Self { - Self { - left: margin, - right: margin, - top: margin, - bottom: margin, - } - } - - /// Margins with the same size on opposing sides - #[inline] - pub const fn symmetric(x: f32, y: f32) -> Self { - Self { - left: x, - right: x, - top: y, - bottom: y, - } - } - - /// Total margins on both sides - #[inline] - pub fn sum(&self) -> Vec2 { - vec2(self.left + self.right, self.top + self.bottom) - } - - #[inline] - pub const fn left_top(&self) -> Vec2 { - vec2(self.left, self.top) - } - - #[inline] - pub const fn right_bottom(&self) -> Vec2 { - vec2(self.right, self.bottom) - } - - #[inline] - pub fn is_same(&self) -> bool { - self.left == self.right && self.left == self.top && self.left == self.bottom - } - - #[inline] - pub fn expand_rect(&self, rect: Rect) -> Rect { - Rect::from_min_max(rect.min - self.left_top(), rect.max + self.right_bottom()) - } - - #[inline] - pub fn shrink_rect(&self, rect: Rect) -> Rect { - Rect::from_min_max(rect.min + self.left_top(), rect.max - self.right_bottom()) - } -} - -impl From for Margin { - #[inline] - fn from(v: f32) -> Self { - Self::same(v) - } -} - -impl From for Margin { - #[inline] - fn from(v: Vec2) -> Self { - Self::symmetric(v.x, v.y) - } -} - -impl std::ops::Add for Margin { - type Output = Self; - - #[inline] - fn add(self, other: Self) -> Self { - Self { - left: self.left + other.left, - right: self.right + other.right, - top: self.top + other.top, - bottom: self.bottom + other.bottom, - } - } -} - -impl std::ops::Add for Margin { - type Output = Self; - - #[inline] - fn add(self, v: f32) -> Self { - Self { - left: self.left + v, - right: self.right + v, - top: self.top + v, - bottom: self.bottom + v, - } - } -} - -impl std::ops::AddAssign for Margin { - #[inline] - fn add_assign(&mut self, v: f32) { - self.left += v; - self.right += v; - self.top += v; - self.bottom += v; - } -} - -impl std::ops::Div for Margin { - type Output = Self; - - #[inline] - fn div(self, v: f32) -> Self { - Self { - left: self.left / v, - right: self.right / v, - top: self.top / v, - bottom: self.bottom / v, - } - } -} - -impl std::ops::DivAssign for Margin { - #[inline] - fn div_assign(&mut self, v: f32) { - self.left /= v; - self.right /= v; - self.top /= v; - self.bottom /= v; - } -} - -impl std::ops::Mul for Margin { - type Output = Self; - - #[inline] - fn mul(self, v: f32) -> Self { - Self { - left: self.left * v, - right: self.right * v, - top: self.top * v, - bottom: self.bottom * v, - } - } -} - -impl std::ops::MulAssign for Margin { - #[inline] - fn mul_assign(&mut self, v: f32) { - self.left *= v; - self.right *= v; - self.top *= v; - self.bottom *= v; - } -} - -impl std::ops::Sub for Margin { - type Output = Self; - - #[inline] - fn sub(self, other: Self) -> Self { - Self { - left: self.left - other.left, - right: self.right - other.right, - top: self.top - other.top, - bottom: self.bottom - other.bottom, - } - } -} - -impl std::ops::Sub for Margin { - type Output = Self; - - #[inline] - fn sub(self, v: f32) -> Self { - Self { - left: self.left - v, - right: self.right - v, - top: self.top - v, - bottom: self.bottom - v, - } - } -} - -impl std::ops::SubAssign for Margin { - #[inline] - fn sub_assign(&mut self, v: f32) { - self.left -= v; - self.right -= v; - self.top -= v; - self.bottom -= v; - } -} - -// ---------------------------------------------------------------------------- - /// How and when interaction happens. #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] diff --git a/crates/epaint/src/lib.rs b/crates/epaint/src/lib.rs index 8746d5647c3..18298484679 100644 --- a/crates/epaint/src/lib.rs +++ b/crates/epaint/src/lib.rs @@ -27,6 +27,7 @@ mod bezier; pub mod image; +mod margin; mod mesh; pub mod mutex; mod shadow; @@ -41,9 +42,10 @@ mod texture_handle; pub mod textures; pub mod util; -pub use { +pub use self::{ bezier::{CubicBezierShape, QuadraticBezierShape}, image::{ColorImage, FontImage, ImageData, ImageDelta}, + margin::Margin, mesh::{Mesh, Mesh16, Vertex}, shadow::Shadow, shape::{ diff --git a/crates/epaint/src/margin.rs b/crates/epaint/src/margin.rs new file mode 100644 index 00000000000..815ff835163 --- /dev/null +++ b/crates/epaint/src/margin.rs @@ -0,0 +1,211 @@ +use emath::{vec2, Rect, Vec2}; + +/// A value for all four sides of a rectangle, +/// often used to express padding or spacing. +#[derive(Clone, Copy, Debug, Default, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub struct Margin { + pub left: f32, + pub right: f32, + pub top: f32, + pub bottom: f32, +} + +impl Margin { + pub const ZERO: Self = Self { + left: 0.0, + right: 0.0, + top: 0.0, + bottom: 0.0, + }; + + #[inline] + pub const fn same(margin: f32) -> Self { + Self { + left: margin, + right: margin, + top: margin, + bottom: margin, + } + } + + /// Margins with the same size on opposing sides + #[inline] + pub const fn symmetric(x: f32, y: f32) -> Self { + Self { + left: x, + right: x, + top: y, + bottom: y, + } + } + + /// Total margins on both sides + #[inline] + pub fn sum(&self) -> Vec2 { + vec2(self.left + self.right, self.top + self.bottom) + } + + #[inline] + pub const fn left_top(&self) -> Vec2 { + vec2(self.left, self.top) + } + + #[inline] + pub const fn right_bottom(&self) -> Vec2 { + vec2(self.right, self.bottom) + } + + #[inline] + pub fn is_same(&self) -> bool { + self.left == self.right && self.left == self.top && self.left == self.bottom + } + + #[inline] + pub fn expand_rect(&self, rect: Rect) -> Rect { + Rect::from_min_max(rect.min - self.left_top(), rect.max + self.right_bottom()) + } + + #[inline] + pub fn shrink_rect(&self, rect: Rect) -> Rect { + Rect::from_min_max(rect.min + self.left_top(), rect.max - self.right_bottom()) + } +} + +impl From for Margin { + #[inline] + fn from(v: f32) -> Self { + Self::same(v) + } +} + +impl From for Margin { + #[inline] + fn from(v: Vec2) -> Self { + Self::symmetric(v.x, v.y) + } +} + +impl std::ops::Add for Margin { + type Output = Self; + + #[inline] + fn add(self, other: Self) -> Self { + Self { + left: self.left + other.left, + right: self.right + other.right, + top: self.top + other.top, + bottom: self.bottom + other.bottom, + } + } +} + +impl std::ops::Add for Margin { + type Output = Self; + + #[inline] + fn add(self, v: f32) -> Self { + Self { + left: self.left + v, + right: self.right + v, + top: self.top + v, + bottom: self.bottom + v, + } + } +} + +impl std::ops::AddAssign for Margin { + #[inline] + fn add_assign(&mut self, v: f32) { + self.left += v; + self.right += v; + self.top += v; + self.bottom += v; + } +} + +impl std::ops::Div for Margin { + type Output = Self; + + #[inline] + fn div(self, v: f32) -> Self { + Self { + left: self.left / v, + right: self.right / v, + top: self.top / v, + bottom: self.bottom / v, + } + } +} + +impl std::ops::DivAssign for Margin { + #[inline] + fn div_assign(&mut self, v: f32) { + self.left /= v; + self.right /= v; + self.top /= v; + self.bottom /= v; + } +} + +impl std::ops::Mul for Margin { + type Output = Self; + + #[inline] + fn mul(self, v: f32) -> Self { + Self { + left: self.left * v, + right: self.right * v, + top: self.top * v, + bottom: self.bottom * v, + } + } +} + +impl std::ops::MulAssign for Margin { + #[inline] + fn mul_assign(&mut self, v: f32) { + self.left *= v; + self.right *= v; + self.top *= v; + self.bottom *= v; + } +} + +impl std::ops::Sub for Margin { + type Output = Self; + + #[inline] + fn sub(self, other: Self) -> Self { + Self { + left: self.left - other.left, + right: self.right - other.right, + top: self.top - other.top, + bottom: self.bottom - other.bottom, + } + } +} + +impl std::ops::Sub for Margin { + type Output = Self; + + #[inline] + fn sub(self, v: f32) -> Self { + Self { + left: self.left - v, + right: self.right - v, + top: self.top - v, + bottom: self.bottom - v, + } + } +} + +impl std::ops::SubAssign for Margin { + #[inline] + fn sub_assign(&mut self, v: f32) { + self.left -= v; + self.right -= v; + self.top -= v; + self.bottom -= v; + } +} From c530504a04252105fd8d0da0af3b5ff2876366f4 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 26 Mar 2024 10:37:12 +0100 Subject: [PATCH 036/134] CSS-like shadows with offset, spread, and blur (#4232) This makes `epaint::Shadow` more like CSS's box-shadow, adding `offset` and replacing `extrusion` with `blur` and `spread`. * Closes https://github.com/emilk/egui/pull/3047 The offsets make for nice drop-shadow effects. Old shadows: old-shadows New shadows: new-shadows-full --- crates/egui/src/containers/area.rs | 2 +- crates/egui/src/style.rs | 33 +++++++-- crates/egui/src/widgets/mod.rs | 47 ++++++++++--- crates/epaint/src/shadow.rs | 103 ++++++++++++++--------------- 4 files changed, 118 insertions(+), 67 deletions(-) diff --git a/crates/egui/src/containers/area.rs b/crates/egui/src/containers/area.rs index 3c6b711be93..c66be433966 100644 --- a/crates/egui/src/containers/area.rs +++ b/crates/egui/src/containers/area.rs @@ -423,7 +423,7 @@ impl Prepared { .at_least(self.state.left_top_pos() + Vec2::splat(32.0)), ); - let shadow_radius = ctx.style().visuals.window_shadow.extrusion; // hacky + let shadow_radius = ctx.style().visuals.window_shadow.margin().sum().max_elem(); // hacky let clip_rect_margin = ctx.style().visuals.clip_rect_margin.max(shadow_radius); let clip_rect = Rect::from_min_max(self.state.left_top_pos(), constrain_rect.max) diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index c270add6362..da507ae2d07 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -1071,7 +1071,12 @@ impl Visuals { error_fg_color: Color32::from_rgb(255, 0, 0), // red window_rounding: Rounding::same(6.0), - window_shadow: Shadow::big_dark(), + window_shadow: Shadow { + offset: vec2(10.0, 20.0), + blur: 15.0, + spread: 0.0, + color: Color32::from_black_alpha(96), + }, window_fill: Color32::from_gray(27), window_stroke: Stroke::new(1.0, Color32::from_gray(60)), window_highlight_topmost: true, @@ -1080,10 +1085,18 @@ impl Visuals { panel_fill: Color32::from_gray(27), - popup_shadow: Shadow::small_dark(), + popup_shadow: Shadow { + offset: vec2(6.0, 10.0), + blur: 8.0, + spread: 0.0, + color: Color32::from_black_alpha(96), + }, + resize_corner_size: 12.0, + text_cursor: Stroke::new(2.0, Color32::from_rgb(192, 222, 255)), text_cursor_preview: false, + clip_rect_margin: 3.0, // should be at least half the size of the widest frame stroke + max WidgetVisuals::expansion button_frame: true, collapsing_header_frame: false, @@ -1115,14 +1128,26 @@ impl Visuals { warn_fg_color: Color32::from_rgb(255, 100, 0), // slightly orange red. it's difficult to find a warning color that pops on bright background. error_fg_color: Color32::from_rgb(255, 0, 0), // red - window_shadow: Shadow::big_light(), + window_shadow: Shadow { + offset: vec2(10.0, 20.0), + blur: 15.0, + spread: 0.0, + color: Color32::from_black_alpha(25), + }, window_fill: Color32::from_gray(248), window_stroke: Stroke::new(1.0, Color32::from_gray(190)), panel_fill: Color32::from_gray(248), - popup_shadow: Shadow::small_light(), + popup_shadow: Shadow { + offset: vec2(6.0, 10.0), + blur: 8.0, + spread: 0.0, + color: Color32::from_black_alpha(25), + }, + text_cursor: Stroke::new(2.0, Color32::from_rgb(0, 83, 125)), + ..Self::dark() } } diff --git a/crates/egui/src/widgets/mod.rs b/crates/egui/src/widgets/mod.rs index d589e386ac7..77ee8692796 100644 --- a/crates/egui/src/widgets/mod.rs +++ b/crates/egui/src/widgets/mod.rs @@ -126,15 +126,44 @@ pub fn stroke_ui(ui: &mut crate::Ui, stroke: &mut epaint::Stroke, text: &str) { } pub(crate) fn shadow_ui(ui: &mut Ui, shadow: &mut epaint::Shadow, text: &str) { - let epaint::Shadow { extrusion, color } = shadow; - ui.horizontal(|ui| { - ui.label(text); - ui.add( - DragValue::new(extrusion) - .speed(1.0) - .clamp_range(0.0..=100.0), - ) - .on_hover_text("Extrusion"); + let epaint::Shadow { + offset, + blur, + spread, + color, + } = shadow; + + ui.label(text); + ui.indent(text, |ui| { + crate::Grid::new("shadow_ui").show(ui, |ui| { + ui.add( + DragValue::new(&mut offset.x) + .speed(1.0) + .clamp_range(-100.0..=100.0) + .prefix("x: "), + ); + ui.add( + DragValue::new(&mut offset.y) + .speed(1.0) + .clamp_range(-100.0..=100.0) + .prefix("y: "), + ); + ui.end_row(); + + ui.add( + DragValue::new(blur) + .speed(1.0) + .clamp_range(0.0..=100.0) + .prefix("Blur:"), + ); + + ui.add( + DragValue::new(spread) + .speed(1.0) + .clamp_range(0.0..=100.0) + .prefix("Spread:"), + ); + }); ui.color_edit_button_srgba(color); }); } diff --git a/crates/epaint/src/shadow.rs b/crates/epaint/src/shadow.rs index fb6a9a4ab24..71ce76c94fe 100644 --- a/crates/epaint/src/shadow.rs +++ b/crates/epaint/src/shadow.rs @@ -1,84 +1,65 @@ use super::*; /// The color and fuzziness of a fuzzy shape. +/// /// Can be used for a rectangular shadow with a soft penumbra. +/// +/// Very similar to a box-shadow in CSS. #[derive(Clone, Copy, Debug, Default, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct Shadow { - /// The shadow extends this much outside the rect. - /// The size of the fuzzy penumbra. - pub extrusion: f32, + /// Move the shadow by this much. + /// + /// For instance, a value of `[1.0, 2.0]` will move the shadow 1 point to the right and 2 points down, + /// causing a drop-shadow effet. + pub offset: Vec2, + + /// The width of the blur, i.e. the width of the fuzzy penumbra. + /// + /// A value of 0.0 means no blur. + pub blur: f32, + + /// Expand the shadow in all directions by this much. + pub spread: f32, /// Color of the opaque center of the shadow. pub color: Color32, } impl Shadow { + /// No shadow at all. pub const NONE: Self = Self { - extrusion: 0.0, + offset: Vec2::ZERO, + blur: 0.0, + spread: 0.0, color: Color32::TRANSPARENT, }; - pub const fn new(extrusion: f32, color: Color32) -> Self { - Self { extrusion, color } - } - - /// Tooltips, menus, …, for dark mode. - pub const fn small_dark() -> Self { - Self { - extrusion: 16.0, - color: Color32::from_black_alpha(96), - } - } - - /// Tooltips, menus, …, for light mode. - pub const fn small_light() -> Self { - Self { - extrusion: 16.0, - color: Color32::from_black_alpha(20), - } - } - - /// Used for egui windows in dark mode. - pub const fn big_dark() -> Self { - Self { - extrusion: 32.0, - color: Color32::from_black_alpha(96), - } - } - - /// Used for egui windows in light mode. - pub const fn big_light() -> Self { - Self { - extrusion: 32.0, - color: Color32::from_black_alpha(16), - } - } - pub fn tessellate(&self, rect: Rect, rounding: impl Into) -> Mesh { // tessellator.clip_rect = clip_rect; // TODO(emilk): culling - let Self { extrusion, color } = *self; + use crate::tessellator::*; - let rounding: Rounding = rounding.into(); - let half_ext = 0.5 * extrusion; + let Self { + offset, + blur, + spread, + color, + } = *self; - let ext_rounding = Rounding { - nw: rounding.nw + half_ext, - ne: rounding.ne + half_ext, - sw: rounding.sw + half_ext, - se: rounding.se + half_ext, - }; + let rect = rect.translate(offset); - use crate::tessellator::*; - let rect = RectShape::filled(rect.expand(half_ext), ext_rounding, color); + let rounding_expansion = spread.abs() + 0.5 * blur; + let rounding = rounding.into() + Rounding::from(rounding_expansion); + + let rect = RectShape::filled(rect.expand(spread), rounding, color); let pixels_per_point = 1.0; // doesn't matter here - let font_tex_size = [1; 2]; // unused size we are not tessellating text. + let font_tex_size = [1; 2]; // unused since we are not tessellating text. let mut tessellator = Tessellator::new( pixels_per_point, TessellationOptions { feathering: true, - feathering_size_in_pixels: extrusion * pixels_per_point, + feathering_size_in_pixels: blur * pixels_per_point, ..Default::default() }, font_tex_size, @@ -88,4 +69,20 @@ impl Shadow { tessellator.tessellate_rect(&rect, &mut mesh); mesh } + + /// How much larger than the parent rect are we in each direction? + pub fn margin(&self) -> Margin { + let Self { + offset, + blur, + spread, + color: _, + } = *self; + Margin { + left: spread + 0.5 * blur - offset.x, + right: spread + 0.5 * blur + offset.x, + top: spread + 0.5 * blur - offset.y, + bottom: spread + 0.5 * blur + offset.y, + } + } } From 9cfaf8b961bdca7d404ed2173b99055998c90753 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 26 Mar 2024 10:54:15 +0100 Subject: [PATCH 037/134] Better align menus with the button that opened them (#4233) Screenshot 2024-03-26 at 10 42 46 Screenshot 2024-03-26 at 10 42 57 --- crates/egui/src/menu.rs | 50 +++++++++++++++++++++++----------------- crates/egui/src/style.rs | 2 +- 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/crates/egui/src/menu.rs b/crates/egui/src/menu.rs index 607b9495a77..d551cd5bbfa 100644 --- a/crates/egui/src/menu.rs +++ b/crates/egui/src/menu.rs @@ -43,11 +43,11 @@ impl BarState { /// Should be called from [`Context`] on a [`Response`] pub fn bar_menu( &mut self, - response: &Response, + button: &Response, add_contents: impl FnOnce(&mut Ui) -> R, ) -> Option> { - MenuRoot::stationary_click_interaction(response, &mut self.open_menu); - self.open_menu.show(response, add_contents) + MenuRoot::stationary_click_interaction(button, &mut self.open_menu); + self.open_menu.show(button, add_contents) } pub(crate) fn has_root(&self) -> bool { @@ -251,11 +251,11 @@ impl MenuRootManager { /// Should be called from [`Context`] on a [`Response`] pub fn show( &mut self, - response: &Response, + button: &Response, add_contents: impl FnOnce(&mut Ui) -> R, ) -> Option> { if let Some(root) = self.inner.as_mut() { - let (menu_response, inner_response) = root.show(response, add_contents); + let (menu_response, inner_response) = root.show(button, add_contents); if MenuResponse::Close == menu_response { self.inner = None; } @@ -301,12 +301,12 @@ impl MenuRoot { pub fn show( &mut self, - response: &Response, + button: &Response, add_contents: impl FnOnce(&mut Ui) -> R, ) -> (MenuResponse, Option>) { - if self.id == response.id { + if self.id == button.id { let inner_response = - MenuState::show(&response.ctx, &self.menu_state, self.id, add_contents); + MenuState::show(&button.ctx, &self.menu_state, self.id, add_contents); let menu_state = self.menu_state.read(); if menu_state.response.is_close() { @@ -319,26 +319,31 @@ impl MenuRoot { /// Interaction with a stationary menu, i.e. fixed in another Ui. /// /// Responds to primary clicks. - fn stationary_interaction(response: &Response, root: &mut MenuRootManager) -> MenuResponse { - let id = response.id; + fn stationary_interaction(button: &Response, root: &mut MenuRootManager) -> MenuResponse { + let id = button.id; - if (response.clicked() && root.is_menu_open(id)) - || response.ctx.input(|i| i.key_pressed(Key::Escape)) + if (button.clicked() && root.is_menu_open(id)) + || button.ctx.input(|i| i.key_pressed(Key::Escape)) { // menu open and button clicked or esc pressed return MenuResponse::Close; - } else if (response.clicked() && !root.is_menu_open(id)) - || (response.hovered() && root.is_some()) + } else if (button.clicked() && !root.is_menu_open(id)) + || (button.hovered() && root.is_some()) { // menu not open and button clicked // or button hovered while other menu is open - let mut pos = response.rect.left_bottom(); + let mut pos = button.rect.left_bottom(); + + let menu_frame = Frame::menu(&button.ctx.style()); + pos.x -= menu_frame.total_margin().left; // Make fist button in menu align with the parent button + pos.y += button.ctx.style().spacing.menu_spacing; + if let Some(root) = root.inner.as_mut() { let menu_rect = root.menu_state.read().rect; - let screen_rect = response.ctx.input(|i| i.screen_rect); + let screen_rect = button.ctx.input(|i| i.screen_rect); if pos.y + menu_rect.height() > screen_rect.max.y { - pos.y = screen_rect.max.y - menu_rect.height() - response.rect.height(); + pos.y = screen_rect.max.y - menu_rect.height() - button.rect.height(); } if pos.x + menu_rect.width() > screen_rect.max.x { @@ -347,11 +352,11 @@ impl MenuRoot { } return MenuResponse::Create(pos, id); - } else if response + } else if button .ctx .input(|i| i.pointer.any_pressed() && i.pointer.primary_down()) { - if let Some(pos) = response.ctx.input(|i| i.pointer.interact_pos()) { + if let Some(pos) = button.ctx.input(|i| i.pointer.interact_pos()) { if let Some(root) = root.inner.as_mut() { if root.id == id { // pressed somewhere while this menu is open @@ -410,8 +415,8 @@ impl MenuRoot { } // Responds to primary clicks. - pub fn stationary_click_interaction(response: &Response, root: &mut MenuRootManager) { - let menu_response = Self::stationary_interaction(response, root); + pub fn stationary_click_interaction(button: &Response, root: &mut MenuRootManager) { + let menu_response = Self::stationary_interaction(button, root); Self::handle_menu_response(root, menu_response); } } @@ -623,8 +628,11 @@ impl MenuState { // ensure to repaint once even when pointer is not moving ui.ctx().request_repaint(); } else if !open && button.hovered() { + // TODO(emilk): open menu to the left if there isn't enough space to the right let mut pos = button.rect.right_top(); pos.x = self.rect.right() + ui.spacing().menu_spacing; + pos.y -= Frame::menu(ui.style()).total_margin().top; // align the first button in the submenu with the parent button + self.open_submenu(sub_id, pos); } else if open && ui.interact_bg(Sense::hover()).contains_pointer() diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index da507ae2d07..5fc3b39ecf3 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -1033,7 +1033,7 @@ impl Default for Spacing { icon_spacing: 4.0, tooltip_width: 600.0, menu_width: 150.0, - menu_spacing: 3.0, + menu_spacing: 2.0, combo_height: 200.0, scroll: Default::default(), indent_ends_with_horizontal_line: false, From 8a880d6d0f851eb06f750364b4c796c0d2853bbc Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 26 Mar 2024 11:12:49 +0100 Subject: [PATCH 038/134] Add `Area::sense` and improve hit-testing of buttons in menus (#4234) Previously, putting the cursor in the gap between two menu buttons would not hover any of the buttons, but the click-sensitive menu itself. Now the menu is no longer click-sensitive. --- crates/egui/src/containers/area.rs | 30 ++++++++++++++++++++++-------- crates/egui/src/menu.rs | 3 ++- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/crates/egui/src/containers/area.rs b/crates/egui/src/containers/area.rs index c66be433966..0c73ddf9fc5 100644 --- a/crates/egui/src/containers/area.rs +++ b/crates/egui/src/containers/area.rs @@ -61,6 +61,7 @@ impl State { #[derive(Clone, Copy, Debug)] pub struct Area { pub(crate) id: Id, + sense: Option, movable: bool, interactable: bool, enabled: bool, @@ -78,6 +79,7 @@ impl Area { pub fn new(id: Id) -> Self { Self { id, + sense: None, movable: true, interactable: true, constrain: false, @@ -114,7 +116,7 @@ impl Area { self } - /// moveable by dragging the area? + /// Moveable by dragging the area? #[inline] pub fn movable(mut self, movable: bool) -> Self { self.movable = movable; @@ -139,6 +141,15 @@ impl Area { self } + /// Explicitly set a sense. + /// + /// If not set, this will default to `Sense::drag()` if movable, `Sense::click()` if interactable, and `Sense::hover()` otherwise. + #[inline] + pub fn sense(mut self, sense: Sense) -> Self { + self.sense = Some(sense); + self + } + /// `order(Order::Foreground)` for an Area that should always be on top #[inline] pub fn order(mut self, order: Order) -> Self { @@ -255,6 +266,7 @@ impl Area { pub(crate) fn begin(self, ctx: &Context) -> Prepared { let Self { id, + sense, movable, order, interactable, @@ -299,13 +311,15 @@ impl Area { // interact right away to prevent frame-delay let mut move_response = { let interact_id = layer_id.id.with("move"); - let sense = if movable { - Sense::drag() - } else if interactable { - Sense::click() // allow clicks to bring to front - } else { - Sense::hover() - }; + let sense = sense.unwrap_or_else(|| { + if movable { + Sense::drag() + } else if interactable { + Sense::click() // allow clicks to bring to front + } else { + Sense::hover() + } + }); let move_response = ctx.create_widget(WidgetRect { id: interact_id, diff --git a/crates/egui/src/menu.rs b/crates/egui/src/menu.rs index d551cd5bbfa..84fd1b2e8e3 100644 --- a/crates/egui/src/menu.rs +++ b/crates/egui/src/menu.rs @@ -149,7 +149,8 @@ pub(crate) fn menu_ui<'c, R>( .order(Order::Foreground) .fixed_pos(pos) .constrain_to(ctx.screen_rect()) - .interactable(true); + .interactable(true) + .sense(Sense::hover()); let area_response = area.show(ctx, |ui| { set_menu_style(ui.style_mut()); From 884cf6de3d003094d539e830a3c8cb077ec40cee Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 26 Mar 2024 11:13:04 +0100 Subject: [PATCH 039/134] Add some tests for `Rect` --- crates/emath/src/rect.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/crates/emath/src/rect.rs b/crates/emath/src/rect.rs index 72331dae1fc..f23d3500520 100644 --- a/crates/emath/src/rect.rs +++ b/crates/emath/src/rect.rs @@ -680,3 +680,22 @@ impl Div for Rect { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_rect() { + let r = Rect::from_min_max(pos2(10.0, 10.0), pos2(20.0, 20.0)); + assert_eq!(r.distance_sq_to_pos(pos2(15.0, 15.0)), 0.0); + assert_eq!(r.distance_sq_to_pos(pos2(10.0, 15.0)), 0.0); + assert_eq!(r.distance_sq_to_pos(pos2(10.0, 10.0)), 0.0); + + assert_eq!(r.distance_sq_to_pos(pos2(5.0, 15.0)), 25.0); // left of + assert_eq!(r.distance_sq_to_pos(pos2(25.0, 15.0)), 25.0); // right of + assert_eq!(r.distance_sq_to_pos(pos2(15.0, 5.0)), 25.0); // above + assert_eq!(r.distance_sq_to_pos(pos2(15.0, 25.0)), 25.0); // below + assert_eq!(r.distance_sq_to_pos(pos2(25.0, 5.0)), 50.0); // right and above + } +} From a03604fce0c3e3489d6b3a7927b4f3ade8f03b03 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 26 Mar 2024 11:13:56 +0100 Subject: [PATCH 040/134] Add breaking change to changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a074c6f11e..30f329a0e43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ Changes since the last release can be found at Date: Tue, 26 Mar 2024 11:15:08 +0100 Subject: [PATCH 041/134] Forbid use of `std::time::Instant` on wasm --- scripts/clippy_wasm/clippy.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/clippy_wasm/clippy.toml b/scripts/clippy_wasm/clippy.toml index aadfe75d83c..0e8b9474ea7 100644 --- a/scripts/clippy_wasm/clippy.toml +++ b/scripts/clippy_wasm/clippy.toml @@ -38,6 +38,7 @@ disallowed-methods = [ disallowed-types = [ { path = "instant::SystemTime", reason = "Known bugs. Use web-time." }, { path = "std::thread::Builder", reason = "Cannot spawn threads on wasm" }, + { path = "std::time::Instant", reason = "Use web-time instead." }, # { path = "std::path::PathBuf", reason = "Can't read/write files on web" }, // TODO(emilk): consider banning Path on wasm ] From f8d7d0ebaa9f991d9796f379fdee9c1014e5d009 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 26 Mar 2024 11:48:24 +0100 Subject: [PATCH 042/134] Enforce writing username in TODO comments (#4235) --- crates/eframe/src/web/events.rs | 4 ++-- crates/egui-winit/src/lib.rs | 4 ++-- crates/egui/src/context.rs | 2 +- crates/egui/src/input_state.rs | 2 +- crates/egui/src/interaction.rs | 4 ++-- crates/egui/src/memory.rs | 2 +- crates/egui/src/text_selection/label_text_selection.rs | 6 +++--- crates/egui/src/widgets/image.rs | 2 +- crates/egui/src/widgets/text_edit/builder.rs | 2 +- crates/egui_demo_lib/src/easy_mark/easy_mark_editor.rs | 2 +- crates/egui_extras/src/loaders.rs | 2 +- crates/egui_plot/src/axis.rs | 4 ++-- crates/egui_plot/src/items/values.rs | 2 +- crates/egui_plot/src/lib.rs | 2 +- crates/epaint/src/stats.rs | 2 +- crates/epaint/src/tessellator.rs | 2 +- crates/epaint/src/text/text_layout.rs | 2 +- examples/test_viewports/src/main.rs | 2 +- scripts/lint.py | 8 +++++++- 19 files changed, 31 insertions(+), 25 deletions(-) diff --git a/crates/eframe/src/web/events.rs b/crates/eframe/src/web/events.rs index 5e5f7b22622..30775733e58 100644 --- a/crates/eframe/src/web/events.rs +++ b/crates/eframe/src/web/events.rs @@ -88,7 +88,7 @@ pub(crate) fn install_document_events(runner_ref: &WebRunner) -> Result<(), JsVa if let Some(key) = egui_key { runner.input.raw.events.push(egui::Event::Key { key, - physical_key: None, // TODO + physical_key: None, // TODO(fornwall) pressed: true, repeat: false, // egui will fill this in for us! modifiers, @@ -155,7 +155,7 @@ pub(crate) fn install_document_events(runner_ref: &WebRunner) -> Result<(), JsVa if let Some(key) = translate_key(&event.key()) { runner.input.raw.events.push(egui::Event::Key { key, - physical_key: None, // TODO + physical_key: None, // TODO(fornwall) pressed: false, repeat: false, modifiers, diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 418c39ece32..41db04952e5 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -308,7 +308,7 @@ impl State { consumed: false, } } - // WindowEvent::TouchpadPressure {device_id, pressure, stage, .. } => {} // TODO + // WindowEvent::TouchpadPressure {device_id, pressure, stage, .. } => {} // TODO(emilk) WindowEvent::Touch(touch) => { self.on_touch(window, touch); let consumed = match touch.phase { @@ -1298,7 +1298,7 @@ fn process_viewport_command( ViewportCommand::StartDrag => { // If `is_viewport_focused` is not checked on x11 the input will be permanently taken until the app is killed! - // TODO: check that the left mouse-button was pressed down recently, + // TODO(emilk): check that the left mouse-button was pressed down recently, // or we will have bugs on Windows. // See https://github.com/emilk/egui/pull/1108 if is_viewport_focused { diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index ea61cd7d034..258c39bfdcf 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -2770,7 +2770,7 @@ impl Context { /// The `Context` lock is held while the given closure is called! /// /// Returns `None` if acesskit is off. - // TODO: consider making both RO and RW versions + // TODO(emilk): consider making both RO and RW versions #[cfg(feature = "accesskit")] pub fn accesskit_node_builder( &self, diff --git a/crates/egui/src/input_state.rs b/crates/egui/src/input_state.rs index 2a196c86528..b2284d45c2e 100644 --- a/crates/egui/src/input_state.rs +++ b/crates/egui/src/input_state.rs @@ -236,7 +236,7 @@ impl InputState { // So we smooth it out over several frames for a nicer user experience when scrolling in egui. unprocessed_scroll_delta += raw_scroll_delta; let dt = stable_dt.at_most(0.1); - let t = crate::emath::exponential_smooth_factor(0.90, 0.1, dt); // reach _% in _ seconds. TODO: parameterize + let t = crate::emath::exponential_smooth_factor(0.90, 0.1, dt); // reach _% in _ seconds. TODO(emilk): parameterize for d in 0..2 { if unprocessed_scroll_delta[d].abs() < 1.0 { diff --git a/crates/egui/src/interaction.rs b/crates/egui/src/interaction.rs index d5812d4d7c2..8ee3f259a43 100644 --- a/crates/egui/src/interaction.rs +++ b/crates/egui/src/interaction.rs @@ -247,8 +247,8 @@ pub(crate) fn interact( hits.click.iter().chain(&hits.drag).map(|w| w.id).collect() } else { // Whatever is topmost is what we are hovering. - // TODO: consider handle hovering over multiple top-most widgets? - // TODO: allow hovering close widgets? + // TODO(emilk): consider handle hovering over multiple top-most widgets? + // TODO(emilk): allow hovering close widgets? hits.contains_pointer .last() .map(|w| w.id) diff --git a/crates/egui/src/memory.rs b/crates/egui/src/memory.rs index 068347b5697..dc611787fff 100644 --- a/crates/egui/src/memory.rs +++ b/crates/egui/src/memory.rs @@ -243,7 +243,7 @@ impl Options { pub fn ui(&mut self, ui: &mut crate::Ui) { let Self { style, // covered above - zoom_factor: _, // TODO + zoom_factor: _, // TODO(emilk) zoom_with_keyboard, tessellation_options, repaint_on_widget_change, diff --git a/crates/egui/src/text_selection/label_text_selection.rs b/crates/egui/src/text_selection/label_text_selection.rs index af9e3039eeb..ec5a4729eee 100644 --- a/crates/egui/src/text_selection/label_text_selection.rs +++ b/crates/egui/src/text_selection/label_text_selection.rs @@ -8,7 +8,7 @@ use super::{ }; /// Turn on to help debug this -const DEBUG: bool = false; // TODO: don't merge this while `true` +const DEBUG: bool = false; // Don't merge `true`! fn paint_selection( ui: &Ui, @@ -169,7 +169,7 @@ impl LabelSelectionState { if ctx.input(|i| i.pointer.any_pressed() && !i.modifiers.shift) { // Maybe a new selection is about to begin, but the old one is over: - // state.selection = None; // TODO: this makes sense, but doesn't work as expected. + // state.selection = None; // TODO(emilk): this makes sense, but doesn't work as expected. } state.selection_bbox_last_frame = state.selection_bbox_this_frame; @@ -562,7 +562,7 @@ impl LabelSelectionState { old.widget_id != new_primary.widget_id || old.ccursor != new_primary.ccursor }); if primary_changed && new_primary.widget_id == widget_id { - let is_fully_visible = ui.clip_rect().contains_rect(response.rect); // TODO: remove this HACK workaround for https://github.com/emilk/egui/issues/1531 + let is_fully_visible = ui.clip_rect().contains_rect(response.rect); // TODO(emilk): remove this HACK workaround for https://github.com/emilk/egui/issues/1531 if selection_changed && !is_fully_visible { // Scroll to keep primary cursor in view: let row_height = estimate_row_height(galley); diff --git a/crates/egui/src/widgets/image.rs b/crates/egui/src/widgets/image.rs index 1d1ec581d8a..90176018cb7 100644 --- a/crates/egui/src/widgets/image.rs +++ b/crates/egui/src/widgets/image.rs @@ -452,7 +452,7 @@ impl ImageSize { } } -// TODO: unit-tests +// TODO(jprochazk): unit-tests fn scale_to_fit(image_size: Vec2, available_size: Vec2, maintain_aspect_ratio: bool) -> Vec2 { if maintain_aspect_ratio { let ratio_x = available_size.x / image_size.x; diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index bed9b6a6d29..e5745389082 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -687,7 +687,7 @@ impl<'t> TextEdit<'t> { let primary_cursor_rect = cursor_rect(galley_pos, &galley, &cursor_range.primary, row_height); - let is_fully_visible = ui.clip_rect().contains_rect(rect); // TODO: remove this HACK workaround for https://github.com/emilk/egui/issues/1531 + let is_fully_visible = ui.clip_rect().contains_rect(rect); // TODO(emilk): remove this HACK workaround for https://github.com/emilk/egui/issues/1531 if (response.changed || selection_changed) && !is_fully_visible { // Scroll to keep primary cursor in view: ui.scroll_to_rect(primary_cursor_rect, None); diff --git a/crates/egui_demo_lib/src/easy_mark/easy_mark_editor.rs b/crates/egui_demo_lib/src/easy_mark/easy_mark_editor.rs index 0971abd45b2..2a44d78f3a7 100644 --- a/crates/egui_demo_lib/src/easy_mark/easy_mark_editor.rs +++ b/crates/egui_demo_lib/src/easy_mark/easy_mark_editor.rs @@ -261,7 +261,7 @@ The style characters are chosen to be similar to what they are representing: `$` = $small$ `^` = ^raised^ -# TODO +# To do - Sub-headers (`## h2`, `### h3` etc) - Hotkey Editor - International keyboard algorithm for non-letter keys diff --git a/crates/egui_extras/src/loaders.rs b/crates/egui_extras/src/loaders.rs index cc775a2ca20..05df9bc8eef 100644 --- a/crates/egui_extras/src/loaders.rs +++ b/crates/egui_extras/src/loaders.rs @@ -1,4 +1,4 @@ -// TODO: automatic cache eviction +// TODO(jprochazk): automatic cache eviction /// Installs a set of image loaders. /// diff --git a/crates/egui_plot/src/axis.rs b/crates/egui_plot/src/axis.rs index df182631c1b..3c2972e6d87 100644 --- a/crates/egui_plot/src/axis.rs +++ b/crates/egui_plot/src/axis.rs @@ -106,7 +106,7 @@ pub struct AxisHints { pub(super) label_spacing: Rangef, } -// TODO: this just a guess. It might cease to work if a user changes font size. +// TODO(JohannesProgrammiert): this just a guess. It might cease to work if a user changes font size. const LINE_HEIGHT: f32 = 12.0; impl AxisHints { @@ -366,7 +366,7 @@ impl AxisWidget { match HPlacement::from(self.hints.placement) { HPlacement::Left => { - let angle = 0.0; // TODO: allow users to rotate text + let angle = 0.0; // TODO(emilk): allow users to rotate text if angle == 0.0 { let x = self.rect.max.x - galley.size().x; diff --git a/crates/egui_plot/src/items/values.rs b/crates/egui_plot/src/items/values.rs index 8ed6d38f333..5f9fe8183f3 100644 --- a/crates/egui_plot/src/items/values.rs +++ b/crates/egui_plot/src/items/values.rs @@ -156,7 +156,7 @@ impl Default for Orientation { pub enum PlotPoints { Owned(Vec), Generator(ExplicitGenerator), - // Borrowed(&[PlotPoint]), // TODO: Lifetimes are tricky in this case. + // Borrowed(&[PlotPoint]), // TODO(EmbersArc): Lifetimes are tricky in this case. } impl Default for PlotPoints { diff --git a/crates/egui_plot/src/lib.rs b/crates/egui_plot/src/lib.rs index ed133d7edb6..e9962c843d2 100644 --- a/crates/egui_plot/src/lib.rs +++ b/crates/egui_plot/src/lib.rs @@ -800,7 +800,7 @@ impl Plot { let plot_id = id.unwrap_or_else(|| ui.make_persistent_id(id_source)); let ([x_axis_widgets, y_axis_widgets], plot_rect) = axis_widgets( - PlotMemory::load(ui.ctx(), plot_id).as_ref(), // TODO: avoid loading plot memory twice + PlotMemory::load(ui.ctx(), plot_id).as_ref(), // TODO(emilk): avoid loading plot memory twice show_axes, complete_rect, [&x_axes, &y_axes], diff --git a/crates/epaint/src/stats.rs b/crates/epaint/src/stats.rs index 3bb51e14170..c63d5a0ddec 100644 --- a/crates/epaint/src/stats.rs +++ b/crates/epaint/src/stats.rs @@ -192,7 +192,7 @@ impl PaintStats { fn add(&mut self, shape: &Shape) { match shape { Shape::Vec(shapes) => { - // self += PaintStats::from_shapes(&shapes); // TODO + // self += PaintStats::from_shapes(&shapes); // TODO(emilk) self.shapes += AllocInfo::from_slice(shapes); self.shape_vec += AllocInfo::from_slice(shapes); for shape in shapes { diff --git a/crates/epaint/src/tessellator.rs b/crates/epaint/src/tessellator.rs index bf419e99b58..49d5ab8a33c 100644 --- a/crates/epaint/src/tessellator.rs +++ b/crates/epaint/src/tessellator.rs @@ -1864,7 +1864,7 @@ impl Tessellator { .filter(|(_, clipped_shape)| should_parallelize(&clipped_shape.shape)) .map(|(index, clipped_shape)| { crate::profile_scope!("tessellate_big_shape"); - // TODO: reuse tessellator in a thread local + // TODO(emilk): reuse tessellator in a thread local let mut tessellator = (*self).clone(); let mut mesh = Mesh::default(); tessellator.tessellate_shape(clipped_shape.shape.clone(), &mut mesh); diff --git a/crates/epaint/src/text/text_layout.rs b/crates/epaint/src/text/text_layout.rs index 9b055edc577..322026da3c5 100644 --- a/crates/epaint/src/text/text_layout.rs +++ b/crates/epaint/src/text/text_layout.rs @@ -960,7 +960,7 @@ fn is_kana(c: char) -> bool { #[inline] fn is_cjk(c: char) -> bool { - // TODO: Add support for Korean Hangul. + // TODO(bigfarts): Add support for Korean Hangul. is_cjk_ideograph(c) || is_kana(c) } diff --git a/examples/test_viewports/src/main.rs b/examples/test_viewports/src/main.rs index 69be9177127..05679668013 100644 --- a/examples/test_viewports/src/main.rs +++ b/examples/test_viewports/src/main.rs @@ -441,7 +441,7 @@ fn drag_source( } } -// TODO: Update to be more like `crates/egui_demo_lib/src/debo/drag_and_drop.rs` +// TODO(emilk): Update to be more like `crates/egui_demo_lib/src/debo/drag_and_drop.rs` fn drop_target( ui: &mut egui::Ui, body: impl FnOnce(&mut egui::Ui) -> R, diff --git a/scripts/lint.py b/scripts/lint.py index 59322ad2819..221d980d079 100755 --- a/scripts/lint.py +++ b/scripts/lint.py @@ -36,7 +36,7 @@ def lint_lines(filepath, lines_in): for line_nr, line in enumerate(lines_in): line_nr = line_nr + 1 - # TODO: only # and /// on lines before a keyword + # TODO(emilk): only # and /// on lines before a keyword pattern = ( r"^\s*((///)|((pub(\(\w*\))? )?((impl|fn|struct|enum|union|trait)\b))).*$" @@ -66,6 +66,12 @@ def lint_lines(filepath, lines_in): ) lines_out.append("#[inline]") + + if re.search(r"TODO[^(]", line): + errors.append( + f"{filepath}:{line_nr}: write 'TODO(username):' instead" + ) + if ( "(target_os" in line and filepath.startswith("./crates/egui/") From 4d4cb3d20db0517e74ca6ab6d3b646f78fd8123b Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 26 Mar 2024 17:13:57 +0100 Subject: [PATCH 043/134] Release 0.27.0 - Nicer menus and new hit test logic --- CHANGELOG.md | 69 ++++++++++++++++++++++++++++++++- Cargo.lock | 24 ++++++------ Cargo.toml | 24 ++++++------ crates/ecolor/CHANGELOG.md | 4 ++ crates/eframe/CHANGELOG.md | 65 ++++++++++++++++++++----------- crates/egui-wgpu/CHANGELOG.md | 4 ++ crates/egui-winit/CHANGELOG.md | 5 +++ crates/egui_extras/CHANGELOG.md | 10 +++++ crates/egui_glow/CHANGELOG.md | 5 +++ crates/egui_plot/CHANGELOG.md | 7 ++++ crates/epaint/CHANGELOG.md | 9 +++++ 11 files changed, 178 insertions(+), 48 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30f329a0e43..128eba907eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,12 +7,79 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. -## Unreleased +## 0.27.0 - 2024-03-26 - Nicer menus and new hit test logic +The hit test logic (what is the user clicking on?) has been completely rewritten, and should now be much more accurate and helpful. +The hit test and interaction logic is run at the start of the frame, using the widgets rects from the previous frame, but the latest mouse coordinates. +It enabled getting a `Response` for a widget _before_ creating it using `Context::read_response`. +This will in the future unlock more powerful widget styling options. +The new hit test also allows clicking slightly outside a button and still hit it, improving the support for touch screens. + +The menus have also been improved so that they both act and feel better, with no change in API. +Included in this is much nicer looking shadows, supporting an offset. + +Screenshot 2024-03-26 at 17 00 23 + ### ⚠️ BREAKING * `Response::clicked*` and `Response::dragged*` may lock the `Context`, so don't call it from a `Context`-locking closure. * `Response::clicked_by` will no longer be true if clicked with keyboard. Use `Response::clicked` instead. * `Memory::focus` has been renamed `Memory::focused` +* `Area::new` now takes an `Id` by argument [#4115](https://github.com/emilk/egui/pull/4115) +* Change the definition of `clicked_by` [#4192](https://github.com/emilk/egui/pull/4192) + +### ☰ Menu related improvements +* Add some distance between parent menu and submenu [#4230](https://github.com/emilk/egui/pull/4230) +* Add `Area::sense` and improve hit-testing of buttons in menus [#4234](https://github.com/emilk/egui/pull/4234) +* Improve logic for when submenus are kept open [#4166](https://github.com/emilk/egui/pull/4166) +* Better align menus with the button that opened them [#4233](https://github.com/emilk/egui/pull/4233) +* Hide hover UI when showing the context menu [#4138](https://github.com/emilk/egui/pull/4138) (thanks [@abey79](https://github.com/abey79)!) +* CSS-like `Shadow` with offset, spread, and blur [#4232](https://github.com/emilk/egui/pull/4232) +* On touch screens, press-and-hold equals a secondary click [#4195](https://github.com/emilk/egui/pull/4195) + +### ⭐ Added +* Add with_taskbar to viewport builder [#3958](https://github.com/emilk/egui/pull/3958) (thanks [@AnotherNathan](https://github.com/AnotherNathan)!) +* Add F21 to F35 key bindings [#4004](https://github.com/emilk/egui/pull/4004) (thanks [@oscargus](https://github.com/oscargus)!) +* Add `Options::debug_paint_interactive_widgets` [#4018](https://github.com/emilk/egui/pull/4018) +* Add `Ui::set_opacity` [#3965](https://github.com/emilk/egui/pull/3965) (thanks [@YgorSouza](https://github.com/YgorSouza)!) +* Add `Response::paint_debug_info()` to make it easy to visualize a widget's id and state [#4056](https://github.com/emilk/egui/pull/4056) (thanks [@abey79](https://github.com/abey79)!) +* Add layer transforms, interaction in layer [#3906](https://github.com/emilk/egui/pull/3906) (thanks [@Tweoss](https://github.com/Tweoss)!) +* Add `ColorImage::from_gray_iter` [#3536](https://github.com/emilk/egui/pull/3536) (thanks [@wangxiaochuTHU](https://github.com/wangxiaochuTHU)!) +* Add API for raw mouse motion [#4063](https://github.com/emilk/egui/pull/4063) (thanks [@GiantBlargg](https://github.com/GiantBlargg)!) +* Add accessibility to `ProgressBar` and `Spinner` [#4139](https://github.com/emilk/egui/pull/4139) (thanks [@DataTriny](https://github.com/DataTriny)!) +* Add x11 window type settings to viewport builder [#4175](https://github.com/emilk/egui/pull/4175) (thanks [@psethwick](https://github.com/psethwick)!) +* Add an API for customizing the return key in TextEdit [#4085](https://github.com/emilk/egui/pull/4085) (thanks [@lemon-sh](https://github.com/lemon-sh)!) +* Convenience `const fn` for `Margin`, `Rounding` and `Shadow` [#4080](https://github.com/emilk/egui/pull/4080) (thanks [@0Qwel](https://github.com/0Qwel)!) +* Serde feature: add serde derives to input related structs [#4100](https://github.com/emilk/egui/pull/4100) (thanks [@gweisert](https://github.com/gweisert)!) +* Give each menu `Area` an id distinct from the id of what was clicked [#4114](https://github.com/emilk/egui/pull/4114) +* `epaint`: Added `Shape::{scale,translate}` wrappers [#4090](https://github.com/emilk/egui/pull/4090) (thanks [@varphone](https://github.com/varphone)!) +* A `Window` can now be resizable in only one direction [#4155](https://github.com/emilk/egui/pull/4155) +* Add `EllipseShape` [#4122](https://github.com/emilk/egui/pull/4122) (thanks [@TheTacBanana](https://github.com/TheTacBanana)!) +* Adjustable Slider rail height [#4092](https://github.com/emilk/egui/pull/4092) (thanks [@rustbasic](https://github.com/rustbasic)!) +* Expose state override for `HeaderResponse` [#4200](https://github.com/emilk/egui/pull/4200) (thanks [@Zeenobit](https://github.com/Zeenobit)!) + +### 🔧 Changed +* `TextEdit`: Change `margin` property to `egui::Margin` type [#3993](https://github.com/emilk/egui/pull/3993) (thanks [@bu5hm4nn](https://github.com/bu5hm4nn)!) +* New widget interaction logic [#4026](https://github.com/emilk/egui/pull/4026) +* `ui.dnd_drop_zone()` now returns `InnerResponse`. [#4079](https://github.com/emilk/egui/pull/4079) (thanks [@sowbug](https://github.com/sowbug)!) +* Support interacting with the background of a `Ui` [#4074](https://github.com/emilk/egui/pull/4074) +* Quickly animate scroll when calling `ui.scroll_to_cursor` etc [#4119](https://github.com/emilk/egui/pull/4119) +* Don't clear modifier state on focus change [#4157](https://github.com/emilk/egui/pull/4157) (thanks [@ming08108](https://github.com/ming08108)!) +* Prevent `egui::Window` from becoming larger than viewport [#4199](https://github.com/emilk/egui/pull/4199) (thanks [@rustbasic](https://github.com/rustbasic)!) +* Don't show URLs when hovering hyperlinks [#4218](https://github.com/emilk/egui/pull/4218) + +### 🐛 Fixed +* Fix incorrect handling of item spacing in `Window` title bar [#3995](https://github.com/emilk/egui/pull/3995) (thanks [@varphone](https://github.com/varphone)!) +* Make `on_disabled_hover_ui` respect `tooltip_delay` [#4012](https://github.com/emilk/egui/pull/4012) (thanks [@YgorSouza](https://github.com/YgorSouza)!) +* Fix `TextEdit` being too short whenever there is horizontal margin [#4005](https://github.com/emilk/egui/pull/4005) (thanks [@gweisert](https://github.com/gweisert)!) +* Fix `Response::interact` and `Ui:interact_with_hovered` [#4013](https://github.com/emilk/egui/pull/4013) +* Fix: `Response.interact_pointer_pos` is `Some` on click and drag released [#4014](https://github.com/emilk/egui/pull/4014) +* Fix custom `Window` `Frame`s [#4009](https://github.com/emilk/egui/pull/4009) (thanks [@varphone](https://github.com/varphone)!) +* Fix: images with background color now respects rounding [#4029](https://github.com/emilk/egui/pull/4029) (thanks [@vincent-sparks](https://github.com/vincent-sparks)!) +* Fixed the incorrect display of the `Window` frame with a wide border or large rounding [#4032](https://github.com/emilk/egui/pull/4032) (thanks [@varphone](https://github.com/varphone)!) +* TextEdit: fix crash when hitting SHIFT + TAB around non-ASCII text [#3984](https://github.com/emilk/egui/pull/3984) (thanks [@rustbasic](https://github.com/rustbasic)!) +* Fix two `ScrollArea` bugs: leaking scroll target and broken animation to target offset [#4174](https://github.com/emilk/egui/pull/4174) (thanks [@abey79](https://github.com/abey79)!) +* Fix bug in `Context::parent_viewport_id` [#4190](https://github.com/emilk/egui/pull/4190) (thanks [@rustbasic](https://github.com/rustbasic)!) +* Remove unnecessary allocation in `RepaintCause::new` [#4146](https://github.com/emilk/egui/pull/4146) (thanks [@valsteen](https://github.com/valsteen)!) ## 0.26.2 - 2024-02-14 diff --git a/Cargo.lock b/Cargo.lock index ba8d5a17a5d..2a517c7059f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1187,7 +1187,7 @@ checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" [[package]] name = "ecolor" -version = "0.26.2" +version = "0.27.0" dependencies = [ "bytemuck", "cint", @@ -1198,7 +1198,7 @@ dependencies = [ [[package]] name = "eframe" -version = "0.26.2" +version = "0.27.0" dependencies = [ "bytemuck", "cocoa", @@ -1236,7 +1236,7 @@ dependencies = [ [[package]] name = "egui" -version = "0.26.2" +version = "0.27.0" dependencies = [ "accesskit", "ahash", @@ -1252,7 +1252,7 @@ dependencies = [ [[package]] name = "egui-wgpu" -version = "0.26.2" +version = "0.27.0" dependencies = [ "bytemuck", "document-features", @@ -1269,7 +1269,7 @@ dependencies = [ [[package]] name = "egui-winit" -version = "0.26.2" +version = "0.27.0" dependencies = [ "accesskit_winit", "arboard", @@ -1287,7 +1287,7 @@ dependencies = [ [[package]] name = "egui_demo_app" -version = "0.26.2" +version = "0.27.0" dependencies = [ "bytemuck", "chrono", @@ -1312,7 +1312,7 @@ dependencies = [ [[package]] name = "egui_demo_lib" -version = "0.26.2" +version = "0.27.0" dependencies = [ "chrono", "criterion", @@ -1327,7 +1327,7 @@ dependencies = [ [[package]] name = "egui_extras" -version = "0.26.2" +version = "0.27.0" dependencies = [ "chrono", "document-features", @@ -1345,7 +1345,7 @@ dependencies = [ [[package]] name = "egui_glow" -version = "0.26.2" +version = "0.27.0" dependencies = [ "bytemuck", "document-features", @@ -1365,7 +1365,7 @@ dependencies = [ [[package]] name = "egui_plot" -version = "0.26.2" +version = "0.27.0" dependencies = [ "document-features", "egui", @@ -1394,7 +1394,7 @@ checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "emath" -version = "0.26.2" +version = "0.27.0" dependencies = [ "bytemuck", "document-features", @@ -1469,7 +1469,7 @@ dependencies = [ [[package]] name = "epaint" -version = "0.26.2" +version = "0.27.0" dependencies = [ "ab_glyph", "ahash", diff --git a/Cargo.toml b/Cargo.toml index c3839499667..431bffcfcc9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ members = [ edition = "2021" license = "MIT OR Apache-2.0" rust-version = "1.72" -version = "0.26.2" +version = "0.27.0" [profile.release] @@ -48,17 +48,17 @@ opt-level = 2 [workspace.dependencies] -emath = { version = "0.26.2", path = "crates/emath", default-features = false } -ecolor = { version = "0.26.2", path = "crates/ecolor", default-features = false } -epaint = { version = "0.26.2", path = "crates/epaint", default-features = false } -egui = { version = "0.26.2", path = "crates/egui", default-features = false } -egui_plot = { version = "0.26.2", path = "crates/egui_plot", default-features = false } -egui-winit = { version = "0.26.2", path = "crates/egui-winit", default-features = false } -egui_extras = { version = "0.26.2", path = "crates/egui_extras", default-features = false } -egui-wgpu = { version = "0.26.2", path = "crates/egui-wgpu", default-features = false } -egui_demo_lib = { version = "0.26.2", path = "crates/egui_demo_lib", default-features = false } -egui_glow = { version = "0.26.2", path = "crates/egui_glow", default-features = false } -eframe = { version = "0.26.2", path = "crates/eframe", default-features = false } +emath = { version = "0.27.0", path = "crates/emath", default-features = false } +ecolor = { version = "0.27.0", path = "crates/ecolor", default-features = false } +epaint = { version = "0.27.0", path = "crates/epaint", default-features = false } +egui = { version = "0.27.0", path = "crates/egui", default-features = false } +egui_plot = { version = "0.27.0", path = "crates/egui_plot", default-features = false } +egui-winit = { version = "0.27.0", path = "crates/egui-winit", default-features = false } +egui_extras = { version = "0.27.0", path = "crates/egui_extras", default-features = false } +egui-wgpu = { version = "0.27.0", path = "crates/egui-wgpu", default-features = false } +egui_demo_lib = { version = "0.27.0", path = "crates/egui_demo_lib", default-features = false } +egui_glow = { version = "0.27.0", path = "crates/egui_glow", default-features = false } +eframe = { version = "0.27.0", path = "crates/eframe", default-features = false } #TODO(emilk): make more things workspace dependencies ahash = { version = "0.8.6", default-features = false, features = [ diff --git a/crates/ecolor/CHANGELOG.md b/crates/ecolor/CHANGELOG.md index 8f60af3df13..d21513d6c15 100644 --- a/crates/ecolor/CHANGELOG.md +++ b/crates/ecolor/CHANGELOG.md @@ -6,6 +6,10 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.27.0 - 2024-03-26 +* Nothing new + + ## 0.26.2 - 2024-02-14 * Nothing new diff --git a/crates/eframe/CHANGELOG.md b/crates/eframe/CHANGELOG.md index f0566955899..3f3c3d689ff 100644 --- a/crates/eframe/CHANGELOG.md +++ b/crates/eframe/CHANGELOG.md @@ -7,6 +7,25 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.27.0 - 2024-03-26 +* Update to document-features 0.2.8 [#4003](https://github.com/emilk/egui/pull/4003) +* Added `App::raw_input_hook` allows for the manipulation or filtering of raw input events [#4008](https://github.com/emilk/egui/pull/4008) (thanks [@varphone](https://github.com/varphone)!) + +#### Desktop/Native +* Add with_taskbar to viewport builder [#3958](https://github.com/emilk/egui/pull/3958) (thanks [@AnotherNathan](https://github.com/AnotherNathan)!) +* Add `winuser` feature to `winapi` to fix unresolved import [#4037](https://github.com/emilk/egui/pull/4037) (thanks [@varphone](https://github.com/varphone)!) +* Add `get_proc_address` in CreationContext [#4145](https://github.com/emilk/egui/pull/4145) (thanks [@Chaojimengnan](https://github.com/Chaojimengnan)!) +* Don't clear modifier state on focus change [#4157](https://github.com/emilk/egui/pull/4157) (thanks [@ming08108](https://github.com/ming08108)!) +* Add x11 window type settings to viewport builder [#4175](https://github.com/emilk/egui/pull/4175) (thanks [@psethwick](https://github.com/psethwick)!) + +#### Web +* Add `webgpu` feature by default to wgpu [#4124](https://github.com/emilk/egui/pull/4124) (thanks [@ctaggart](https://github.com/ctaggart)!) +* Update kb modifiers from web mouse events [#4156](https://github.com/emilk/egui/pull/4156) (thanks [@ming08108](https://github.com/ming08108)!) +* Fix crash on `request_animation_frame` when destroying web runner [#4169](https://github.com/emilk/egui/pull/4169) (thanks [@jprochazk](https://github.com/jprochazk)!) +* Fix bug parsing url query with escaped & or = [#4172](https://github.com/emilk/egui/pull/4172) +* `Location::query_map`: support repeated key [#4183](https://github.com/emilk/egui/pull/4183) + + ## 0.26.2 - 2024-02-14 * Add `winuser` feature to `winapi` to fix unresolved import [#4037](https://github.com/emilk/egui/pull/4037) (thanks [@varphone](https://github.com/varphone)!) @@ -22,38 +41,38 @@ Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.27.0 - 2024-03-26 +* Improve panic message in egui-wgpu when failing to create buffers [#3986](https://github.com/emilk/egui/pull/3986) + + ## 0.26.2 - 2024-02-14 * Nothing new diff --git a/crates/egui-winit/CHANGELOG.md b/crates/egui-winit/CHANGELOG.md index 95961377e68..ff85f8b5dcb 100644 --- a/crates/egui-winit/CHANGELOG.md +++ b/crates/egui-winit/CHANGELOG.md @@ -5,6 +5,11 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.27.0 - 2024-03-26 +* Update memoffset to 0.9.0, arboard to 3.3.1, and remove egui_glow's needless dependency on pure_glow's deps [#4036](https://github.com/emilk/egui/pull/4036) (thanks [@Nopey](https://github.com/Nopey)!) +* Don't clear modifier state on focus change [#4157](https://github.com/emilk/egui/pull/4157) (thanks [@ming08108](https://github.com/ming08108)!) + + ## 0.26.2 - 2024-02-14 * Update memoffset to 0.9.0, arboard to 3.3.1, and remove egui_glow's needless dependency on pure_glow's deps [#4036](https://github.com/emilk/egui/pull/4036) (thanks [@Nopey](https://github.com/Nopey)!) diff --git a/crates/egui_extras/CHANGELOG.md b/crates/egui_extras/CHANGELOG.md index 5863612d76a..fd498f3773a 100644 --- a/crates/egui_extras/CHANGELOG.md +++ b/crates/egui_extras/CHANGELOG.md @@ -5,6 +5,16 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.27.0 - 2024-03-26 +* Add scroll bar visibility option to `Table` widget [#3981](https://github.com/emilk/egui/pull/3981) (thanks [@richardhozak](https://github.com/richardhozak)!) +* Update `ehttp` to 0.5 [#4055](https://github.com/emilk/egui/pull/4055) +* Fix: assign a different id to each table cell, avoiding id clashes [#4076](https://github.com/emilk/egui/pull/4076) +* Fix interaction with widgets inside selectable rows of `Table` [#4077](https://github.com/emilk/egui/pull/4077) +* Fixed handling of `file://` protocol for images [#4107](https://github.com/emilk/egui/pull/4107) (thanks [@varphone](https://github.com/varphone)!) +* Option to change date picker format [#4180](https://github.com/emilk/egui/pull/4180) (thanks [@zaaarf](https://github.com/zaaarf)!) +* Added ability to disable highlighting of weekend days in `DatePickerPopup`. [#4151](https://github.com/emilk/egui/pull/4151) (thanks [@hiyosilver](https://github.com/hiyosilver)!) + + ## 0.26.2 - 2024-02-14 * Nothing new diff --git a/crates/egui_glow/CHANGELOG.md b/crates/egui_glow/CHANGELOG.md index abb7a71292a..970fb87a2b2 100644 --- a/crates/egui_glow/CHANGELOG.md +++ b/crates/egui_glow/CHANGELOG.md @@ -6,6 +6,11 @@ Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.27.0 - 2024-03-26 +* Add `sense` option to `Plot` [#4052](https://github.com/emilk/egui/pull/4052) (thanks [@AmesingFlank](https://github.com/AmesingFlank)!) +* Plot widget - allow disabling scroll for x and y separately [#4051](https://github.com/emilk/egui/pull/4051) (thanks [@YgorSouza](https://github.com/YgorSouza)!) +* Fix panic when the base step size is set to 0 [#4078](https://github.com/emilk/egui/pull/4078) (thanks [@abey79](https://github.com/abey79)!) +* Expose `PlotGeometry` in public API [#4193](https://github.com/emilk/egui/pull/4193) (thanks [@dwuertz](https://github.com/dwuertz)!) + + ## 0.26.2 - 2024-02-14 * Nothing new diff --git a/crates/epaint/CHANGELOG.md b/crates/epaint/CHANGELOG.md index 02c2bc66641..4e222f85563 100644 --- a/crates/epaint/CHANGELOG.md +++ b/crates/epaint/CHANGELOG.md @@ -5,6 +5,15 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.27.0 - 2024-03-26 +* Add `ColorImage::from_gray_iter` [#3536](https://github.com/emilk/egui/pull/3536) (thanks [@wangxiaochuTHU](https://github.com/wangxiaochuTHU)!) +* Convenience const fn for `Margin`, `Rounding` and `Shadow` [#4080](https://github.com/emilk/egui/pull/4080) (thanks [@0Qwel](https://github.com/0Qwel)!) +* Added `Shape::{scale,translate}` wrappers [#4090](https://github.com/emilk/egui/pull/4090) (thanks [@varphone](https://github.com/varphone)!) +* Add `EllipseShape` [#4122](https://github.com/emilk/egui/pull/4122) (thanks [@TheTacBanana](https://github.com/TheTacBanana)!) +* Add `Margin` to `epaint` [#4231](https://github.com/emilk/egui/pull/4231) +* CSS-like `Shadow` with offset, spread, and blur [#4232](https://github.com/emilk/egui/pull/4232) + + ## 0.26.2 - 2024-02-14 * Nothing new From 947b5813d7def12be84e3c2f3cf4af13a1eba5b3 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 27 Mar 2024 10:13:49 +0100 Subject: [PATCH 044/134] Fix visual glitch on the right side of highly rounded rectangles (#4244) * Part of https://github.com/emilk/egui/issues/4238 When one side of a rectangle is all rounding we need to take care not to produce duplicated vertices in the rectangle path generator. The old code only handled three sides, but forgot the last side (the right side). The new code handles the right side, and also handles the other sides more robustly (with a floating point eps) and efficiently (in a single pass). The glitch was most notable in shadows with a high blur width. Examples of the glitch: Screenshot 2024-03-26 at 20 15 38 Screenshot 2024-03-27 at 09 48 48 Screenshot 2024-03-27 at 09 49 21 --- crates/epaint/src/tessellator.rs | 36 +++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/crates/epaint/src/tessellator.rs b/crates/epaint/src/tessellator.rs index 49d5ab8a33c..f626b9f3094 100644 --- a/crates/epaint/src/tessellator.rs +++ b/crates/epaint/src/tessellator.rs @@ -520,7 +520,7 @@ pub mod path { let min = rect.min; let max = rect.max; - let r = clamp_radius(rounding, rect); + let r = clamp_rounding(rounding, rect); if r == Rounding::ZERO { let min = rect.min; @@ -531,11 +531,33 @@ pub mod path { path.push(pos2(max.x, max.y)); // right bottom path.push(pos2(min.x, max.y)); // left bottom } else { - add_circle_quadrant(path, pos2(max.x - r.se, max.y - r.se), r.se, 0.0); - add_circle_quadrant(path, pos2(min.x + r.sw, max.y - r.sw), r.sw, 1.0); - add_circle_quadrant(path, pos2(min.x + r.nw, min.y + r.nw), r.nw, 2.0); - add_circle_quadrant(path, pos2(max.x - r.ne, min.y + r.ne), r.ne, 3.0); - path.dedup(); // We get duplicates for thin rectangles, producing visual artifats + // We need to avoid duplicated vertices, because that leads to visual artifacts later. + // Duplicated vertices can happen when one side is all rounding, with no straight edge between. + let eps = f32::EPSILON * rect.size().max_elem(); + + add_circle_quadrant(path, pos2(max.x - r.se, max.y - r.se), r.se, 0.0); // south east + + if rect.width() <= r.se + r.sw + eps { + path.pop(); // avoid duplicated vertex + } + + add_circle_quadrant(path, pos2(min.x + r.sw, max.y - r.sw), r.sw, 1.0); // south west + + if rect.height() <= r.sw + r.nw + eps { + path.pop(); // avoid duplicated vertex + } + + add_circle_quadrant(path, pos2(min.x + r.nw, min.y + r.nw), r.nw, 2.0); // north west + + if rect.width() <= r.nw + r.ne + eps { + path.pop(); // avoid duplicated vertex + } + + add_circle_quadrant(path, pos2(max.x - r.ne, min.y + r.ne), r.ne, 3.0); // north east + + if rect.height() <= r.ne + r.se + eps { + path.pop(); // avoid duplicated vertex + } } } @@ -589,7 +611,7 @@ pub mod path { } // Ensures the radius of each corner is within a valid range - fn clamp_radius(rounding: Rounding, rect: Rect) -> Rounding { + fn clamp_rounding(rounding: Rounding, rect: Rect) -> Rounding { let half_width = rect.width() * 0.5; let half_height = rect.height() * 0.5; let max_cr = half_width.min(half_height); From a15e6c21220e593932569558a31de9403e3791d0 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 27 Mar 2024 11:22:38 +0100 Subject: [PATCH 045/134] Prevent visual glitch when shadow blur width is very high (#4245) * Closes https://github.com/emilk/egui/issues/4238 The comment in the code explains it well, but the short of it is this: we can't handle a shadow blur width larger than the shadow rectangle, so we need to clamp the blur. This means smaller things will cast shadows with a smaller blur width, but that's better than having visual glitches. --- crates/epaint/src/shadow.rs | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/crates/epaint/src/shadow.rs b/crates/epaint/src/shadow.rs index 71ce76c94fe..8b73c07e0da 100644 --- a/crates/epaint/src/shadow.rs +++ b/crates/epaint/src/shadow.rs @@ -1,3 +1,5 @@ +use emath::NumExt as _; + use super::*; /// The color and fuzziness of a fuzzy shape. @@ -16,7 +18,7 @@ pub struct Shadow { /// The width of the blur, i.e. the width of the fuzzy penumbra. /// - /// A value of 0.0 means no blur. + /// A value of 0.0 means a sharp shadow. pub blur: f32, /// Expand the shadow in all directions by this much. @@ -47,12 +49,26 @@ impl Shadow { color, } = *self; - let rect = rect.translate(offset); + let rect = rect.translate(offset).expand(spread); + + // We simulate a blurry shadow by tessellating a solid rectangle using a very large feathering. + // Feathering is usually used to make the edges of a shape softer for anti-aliasing. + // The tessellator can't handle blurring/feathering larger than the smallest side of the rect. + // Thats because the tessellator approximate very thin rectangles as line segments, + // and these line segments don't have rounded corners. + // When the feathering is small (the size of a pixel), this is usually fine, + // but here we have a huge feathering to simulate blur, + // so we need to avoid this optimization in the tessellator, + // which is also why we add this rather big epsilon: + let eps = 0.1; + let blur = blur.at_most(rect.size().min_elem() - eps).at_least(0.0); + + // TODO(emilk): if blur <= 0, return a simple `Shape::Rect` instead of using the tessellator let rounding_expansion = spread.abs() + 0.5 * blur; - let rounding = rounding.into() + Rounding::from(rounding_expansion); + let rounding = rounding.into() + Rounding::same(rounding_expansion); - let rect = RectShape::filled(rect.expand(spread), rounding, color); + let rect = RectShape::filled(rect, rounding, color); let pixels_per_point = 1.0; // doesn't matter here let font_tex_size = [1; 2]; // unused since we are not tessellating text. let mut tessellator = Tessellator::new( From bc5ce7781906a2227bcf367292c500f8e7713117 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 27 Mar 2024 16:14:17 +0100 Subject: [PATCH 046/134] Fix `InputState::any_touches` and add `InputState::has_touch_screen` (#4247) Add `InputState::has_touch_screen` to query if there ever has been any touches (which is what `any_touches` used to return). --- crates/egui/src/input_state.rs | 5 +++++ crates/egui/src/input_state/touch_state.rs | 5 +++++ crates/egui/src/widgets/label.rs | 2 +- crates/egui/src/widgets/text_edit/builder.rs | 2 +- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/crates/egui/src/input_state.rs b/crates/egui/src/input_state.rs index b2284d45c2e..a9c1b314383 100644 --- a/crates/egui/src/input_state.rs +++ b/crates/egui/src/input_state.rs @@ -485,6 +485,11 @@ impl InputState { /// True if there currently are any fingers touching egui. pub fn any_touches(&self) -> bool { + self.touch_states.values().any(|t| t.any_touches()) + } + + /// True if we have ever received a touch event. + pub fn has_touch_screen(&self) -> bool { !self.touch_states.is_empty() } diff --git a/crates/egui/src/input_state/touch_state.rs b/crates/egui/src/input_state/touch_state.rs index 2a77a4d371b..43053f821e5 100644 --- a/crates/egui/src/input_state/touch_state.rs +++ b/crates/egui/src/input_state/touch_state.rs @@ -177,6 +177,11 @@ impl TouchState { } } + /// Are there currently any fingers touching the surface? + pub fn any_touches(&self) -> bool { + !self.active_touches.is_empty() + } + pub fn info(&self) -> Option { self.gesture_state.as_ref().map(|state| { // state.previous can be `None` when the number of simultaneous touches has just diff --git a/crates/egui/src/widgets/label.rs b/crates/egui/src/widgets/label.rs index f5bb7ac425a..10cefc5fb6a 100644 --- a/crates/egui/src/widgets/label.rs +++ b/crates/egui/src/widgets/label.rs @@ -128,7 +128,7 @@ impl Label { // dragging select text, or scroll the enclosing [`ScrollArea`] (if any)? // Since currently copying selected text in not supported on `eframe` web, // we prioritize touch-scrolling: - let allow_drag_to_select = ui.input(|i| !i.any_touches()); + let allow_drag_to_select = ui.input(|i| !i.has_touch_screen()); let mut select_sense = if allow_drag_to_select { Sense::click_and_drag() diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index e5745389082..799774096ff 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -527,7 +527,7 @@ impl<'t> TextEdit<'t> { // Since currently copying selected text in not supported on `eframe` web, // we prioritize touch-scrolling: let allow_drag_to_select = - ui.input(|i| !i.any_touches()) || ui.memory(|mem| mem.has_focus(id)); + ui.input(|i| !i.has_touch_screen()) || ui.memory(|mem| mem.has_focus(id)); let sense = if interactive { if allow_drag_to_select { From 3c029a45aca5257fc59640b1df8015f5893de559 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 27 Mar 2024 16:14:22 +0100 Subject: [PATCH 047/134] Fix `Context::repaint_causes` returning no causes (#4248) It would return the causes for repainting again collected this frame, instead of the cause for repainting the current frame. * Part of https://github.com/emilk/egui/issues/3931 --- crates/egui/src/context.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 258c39bfdcf..376c41d94d4 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -252,7 +252,7 @@ struct ViewportState { } /// What called [`Context::request_repaint`]? -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct RepaintCause { /// What file had the call that requested the repaint? pub file: &'static str, @@ -261,6 +261,12 @@ pub struct RepaintCause { pub line: u32, } +impl std::fmt::Debug for RepaintCause { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}:{}", self.file, self.line) + } +} + impl RepaintCause { /// Capture the file and line number of the call site. #[allow(clippy::new_without_default)] @@ -1465,7 +1471,7 @@ impl Context { self.read(|ctx| { ctx.viewports .get(&ctx.viewport_id()) - .map(|v| v.repaint.causes.clone()) + .map(|v| v.repaint.prev_causes.clone()) }) .unwrap_or_default() } From 570e7cf71bbba6376138b20df857496adc886a64 Mon Sep 17 00:00:00 2001 From: lomekragow <51994883+Chaojimengnan@users.noreply.github.com> Date: Wed, 27 Mar 2024 23:35:25 +0800 Subject: [PATCH 048/134] Add `register_native_texture` in `eframe::Frame` (#4246) * Closes https://github.com/emilk/egui/issues/4243 --------- Co-authored-by: Emil Ernerfeldt --- crates/eframe/src/epi.rs | 14 ++++++++++++++ crates/eframe/src/native/epi_integration.rs | 5 +++++ crates/eframe/src/native/glow_integration.rs | 6 +++++- crates/eframe/src/native/wgpu_integration.rs | 2 ++ 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/crates/eframe/src/epi.rs b/crates/eframe/src/epi.rs index 5ee29e457fe..e7b53691cda 100644 --- a/crates/eframe/src/epi.rs +++ b/crates/eframe/src/epi.rs @@ -614,6 +614,11 @@ pub struct Frame { #[cfg(feature = "glow")] pub(crate) gl: Option>, + /// Used to convert user custom [`glow::Texture`] to [`egui::TextureId`] + #[cfg(all(feature = "glow", not(target_arch = "wasm32")))] + pub(crate) glow_register_native_texture: + Option egui::TextureId>>, + /// Can be used to manage GPU resources for custom rendering with WGPU using [`egui::PaintCallback`]s. #[cfg(feature = "wgpu")] pub(crate) wgpu_render_state: Option, @@ -690,6 +695,15 @@ impl Frame { self.gl.as_ref() } + /// Register your own [`glow::Texture`], + /// and then you can use the returned [`egui::TextureId`] to render your texture with [`egui`]. + /// + /// This function will take the ownership of your [`glow::Texture`], so please do not delete your [`glow::Texture`] after registering. + #[cfg(all(feature = "glow", not(target_arch = "wasm32")))] + pub fn register_native_glow_texture(&mut self, native: glow::Texture) -> egui::TextureId { + self.glow_register_native_texture.as_mut().unwrap()(native) + } + /// The underlying WGPU render state. /// /// Only available when compiling with the `wgpu` feature and using [`Renderer::Wgpu`]. diff --git a/crates/eframe/src/native/epi_integration.rs b/crates/eframe/src/native/epi_integration.rs index f27d011202e..fbf7b6dc078 100644 --- a/crates/eframe/src/native/epi_integration.rs +++ b/crates/eframe/src/native/epi_integration.rs @@ -152,6 +152,9 @@ impl EpiIntegration { native_options: &crate::NativeOptions, storage: Option>, #[cfg(feature = "glow")] gl: Option>, + #[cfg(feature = "glow")] glow_register_native_texture: Option< + Box egui::TextureId>, + >, #[cfg(feature = "wgpu")] wgpu_render_state: Option, ) -> Self { let frame = epi::Frame { @@ -162,6 +165,8 @@ impl EpiIntegration { storage, #[cfg(feature = "glow")] gl, + #[cfg(feature = "glow")] + glow_register_native_texture, #[cfg(feature = "wgpu")] wgpu_render_state, raw_display_handle: window.display_handle().map(|h| h.as_raw()), diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index 8b1f16ec807..8dd0f05f6e2 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -217,6 +217,7 @@ impl GlowWinitApp { let system_theme = winit_integration::system_theme(&glutin.window(ViewportId::ROOT), &self.native_options); + let painter = Rc::new(RefCell::new(painter)); let integration = EpiIntegration::new( egui_ctx, @@ -226,6 +227,10 @@ impl GlowWinitApp { &self.native_options, storage, Some(gl.clone()), + Some(Box::new({ + let painter = painter.clone(); + move |native| painter.borrow_mut().register_native_texture(native) + })), #[cfg(feature = "wgpu")] None, ); @@ -302,7 +307,6 @@ impl GlowWinitApp { }; let glutin = Rc::new(RefCell::new(glutin)); - let painter = Rc::new(RefCell::new(painter)); { // Create weak pointers so that we don't keep diff --git a/crates/eframe/src/native/wgpu_integration.rs b/crates/eframe/src/native/wgpu_integration.rs index 61bf157c007..c17bba27afa 100644 --- a/crates/eframe/src/native/wgpu_integration.rs +++ b/crates/eframe/src/native/wgpu_integration.rs @@ -215,6 +215,8 @@ impl WgpuWinitApp { storage, #[cfg(feature = "glow")] None, + #[cfg(feature = "glow")] + None, wgpu_render_state.clone(), ); From 9fa8aa7e30e3d2cdf67dd4d9ab023ea3eed4c225 Mon Sep 17 00:00:00 2001 From: Nicolas PASCAL Date: Wed, 27 Mar 2024 16:35:36 +0100 Subject: [PATCH 049/134] `Plot::Items:allow_hover` give possibility to masked the interaction on hovered item (#2558) This is particularly interesting if you want to authorize a single hover tooltip on an item in the event of an interaction. --------- Co-authored-by: Emil Ernerfeldt --- crates/egui_plot/src/items/mod.rs | 134 ++++++++++++++++++++++++++++++ crates/egui_plot/src/lib.rs | 15 ++-- 2 files changed, 143 insertions(+), 6 deletions(-) diff --git a/crates/egui_plot/src/items/mod.rs b/crates/egui_plot/src/items/mod.rs index 3df3b798dd3..78f4560c6d3 100644 --- a/crates/egui_plot/src/items/mod.rs +++ b/crates/egui_plot/src/items/mod.rs @@ -45,6 +45,9 @@ pub trait PlotItem { fn highlighted(&self) -> bool; + /// Can the user hover this is item? + fn allow_hover(&self) -> bool; + fn geometry(&self) -> PlotGeometry<'_>; fn bounds(&self) -> PlotBounds; @@ -121,6 +124,7 @@ pub struct HLine { pub(super) stroke: Stroke, pub(super) name: String, pub(super) highlight: bool, + pub(super) allow_hover: bool, pub(super) style: LineStyle, id: Option, } @@ -132,6 +136,7 @@ impl HLine { stroke: Stroke::new(1.0, Color32::TRANSPARENT), name: String::default(), highlight: false, + allow_hover: true, style: LineStyle::Solid, id: None, } @@ -144,6 +149,13 @@ impl HLine { self } + /// Allowed hovering this item in the plot. Default: `true`. + #[inline] + pub fn allow_hover(mut self, hovering: bool) -> Self { + self.allow_hover = hovering; + self + } + /// Add a stroke. #[inline] pub fn stroke(mut self, stroke: impl Into) -> Self { @@ -233,6 +245,10 @@ impl PlotItem for HLine { self.highlight } + fn allow_hover(&self) -> bool { + self.allow_hover + } + fn geometry(&self) -> PlotGeometry<'_> { PlotGeometry::None } @@ -256,6 +272,7 @@ pub struct VLine { pub(super) stroke: Stroke, pub(super) name: String, pub(super) highlight: bool, + pub(super) allow_hover: bool, pub(super) style: LineStyle, id: Option, } @@ -267,6 +284,7 @@ impl VLine { stroke: Stroke::new(1.0, Color32::TRANSPARENT), name: String::default(), highlight: false, + allow_hover: true, style: LineStyle::Solid, id: None, } @@ -279,6 +297,13 @@ impl VLine { self } + /// Allowed hovering this item in the plot. Default: `true`. + #[inline] + pub fn allow_hover(mut self, hovering: bool) -> Self { + self.allow_hover = hovering; + self + } + /// Add a stroke. #[inline] pub fn stroke(mut self, stroke: impl Into) -> Self { @@ -368,6 +393,10 @@ impl PlotItem for VLine { self.highlight } + fn allow_hover(&self) -> bool { + self.allow_hover + } + fn geometry(&self) -> PlotGeometry<'_> { PlotGeometry::None } @@ -390,6 +419,7 @@ pub struct Line { pub(super) stroke: Stroke, pub(super) name: String, pub(super) highlight: bool, + pub(super) allow_hover: bool, pub(super) fill: Option, pub(super) style: LineStyle, id: Option, @@ -402,6 +432,7 @@ impl Line { stroke: Stroke::new(1.5, Color32::TRANSPARENT), // Note: a stroke of 1.0 (or less) can look bad on low-dpi-screens name: Default::default(), highlight: false, + allow_hover: true, fill: None, style: LineStyle::Solid, id: None, @@ -415,6 +446,13 @@ impl Line { self } + /// Allowed hovering this item in the plot. Default: `true`. + #[inline] + pub fn allow_hover(mut self, hovering: bool) -> Self { + self.allow_hover = hovering; + self + } + /// Add a stroke. #[inline] pub fn stroke(mut self, stroke: impl Into) -> Self { @@ -558,6 +596,10 @@ impl PlotItem for Line { self.highlight } + fn allow_hover(&self) -> bool { + self.allow_hover + } + fn geometry(&self) -> PlotGeometry<'_> { PlotGeometry::Points(self.series.points()) } @@ -577,6 +619,7 @@ pub struct Polygon { pub(super) stroke: Stroke, pub(super) name: String, pub(super) highlight: bool, + pub(super) allow_hover: bool, pub(super) fill_color: Option, pub(super) style: LineStyle, id: Option, @@ -589,6 +632,7 @@ impl Polygon { stroke: Stroke::new(1.0, Color32::TRANSPARENT), name: Default::default(), highlight: false, + allow_hover: true, fill_color: None, style: LineStyle::Solid, id: None, @@ -603,6 +647,13 @@ impl Polygon { self } + /// Allowed hovering this item in the plot. Default: `true`. + #[inline] + pub fn allow_hover(mut self, hovering: bool) -> Self { + self.allow_hover = hovering; + self + } + /// Add a custom stroke. #[inline] pub fn stroke(mut self, stroke: impl Into) -> Self { @@ -697,6 +748,10 @@ impl PlotItem for Polygon { self.highlight } + fn allow_hover(&self) -> bool { + self.allow_hover + } + fn geometry(&self) -> PlotGeometry<'_> { PlotGeometry::Points(self.series.points()) } @@ -717,6 +772,7 @@ pub struct Text { pub(super) position: PlotPoint, pub(super) name: String, pub(super) highlight: bool, + pub(super) allow_hover: bool, pub(super) color: Color32, pub(super) anchor: Align2, id: Option, @@ -729,6 +785,7 @@ impl Text { position, name: Default::default(), highlight: false, + allow_hover: true, color: Color32::TRANSPARENT, anchor: Align2::CENTER_CENTER, id: None, @@ -742,6 +799,13 @@ impl Text { self } + /// Allowed hovering this item in the plot. Default: `true`. + #[inline] + pub fn allow_hover(mut self, hovering: bool) -> Self { + self.allow_hover = hovering; + self + } + /// Text color. #[inline] pub fn color(mut self, color: impl Into) -> Self { @@ -822,6 +886,10 @@ impl PlotItem for Text { self.highlight } + fn allow_hover(&self) -> bool { + self.allow_hover + } + fn geometry(&self) -> PlotGeometry<'_> { PlotGeometry::None } @@ -856,6 +924,8 @@ pub struct Points { pub(super) highlight: bool, + pub(super) allow_hover: bool, + pub(super) stems: Option, id: Option, } @@ -870,6 +940,7 @@ impl Points { radius: 1.0, name: Default::default(), highlight: false, + allow_hover: true, stems: None, id: None, } @@ -889,6 +960,13 @@ impl Points { self } + /// Allowed hovering this item in the plot. Default: `true`. + #[inline] + pub fn allow_hover(mut self, hovering: bool) -> Self { + self.allow_hover = hovering; + self + } + /// Set the marker's color. #[inline] pub fn color(mut self, color: impl Into) -> Self { @@ -1087,6 +1165,10 @@ impl PlotItem for Points { self.highlight } + fn allow_hover(&self) -> bool { + self.allow_hover + } + fn geometry(&self) -> PlotGeometry<'_> { PlotGeometry::Points(self.series.points()) } @@ -1108,6 +1190,7 @@ pub struct Arrows { pub(super) color: Color32, pub(super) name: String, pub(super) highlight: bool, + pub(super) allow_hover: bool, id: Option, } @@ -1120,6 +1203,7 @@ impl Arrows { color: Color32::TRANSPARENT, name: Default::default(), highlight: false, + allow_hover: true, id: None, } } @@ -1131,6 +1215,13 @@ impl Arrows { self } + /// Allowed hovering this item in the plot. Default: `true`. + #[inline] + pub fn allow_hover(mut self, hovering: bool) -> Self { + self.allow_hover = hovering; + self + } + /// Set the length of the arrow tips #[inline] pub fn tip_length(mut self, tip_length: f32) -> Self { @@ -1232,6 +1323,10 @@ impl PlotItem for Arrows { self.highlight } + fn allow_hover(&self) -> bool { + self.allow_hover + } + fn geometry(&self) -> PlotGeometry<'_> { PlotGeometry::Points(self.origins.points()) } @@ -1256,6 +1351,7 @@ pub struct PlotImage { pub(super) bg_fill: Color32, pub(super) tint: Color32, pub(super) highlight: bool, + pub(super) allow_hover: bool, pub(super) name: String, id: Option, } @@ -1271,6 +1367,7 @@ impl PlotImage { position: center_position, name: Default::default(), highlight: false, + allow_hover: true, texture_id: texture_id.into(), uv: Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0)), size: size.into(), @@ -1288,6 +1385,13 @@ impl PlotImage { self } + /// Allowed hovering this item in the plot. Default: `true`. + #[inline] + pub fn allow_hover(mut self, hovering: bool) -> Self { + self.allow_hover = hovering; + self + } + /// Select UV range. Default is (0,0) in top-left, (1,1) bottom right. #[inline] pub fn uv(mut self, uv: impl Into) -> Self { @@ -1407,6 +1511,10 @@ impl PlotItem for PlotImage { self.highlight } + fn allow_hover(&self) -> bool { + self.allow_hover + } + fn geometry(&self) -> PlotGeometry<'_> { PlotGeometry::None } @@ -1443,6 +1551,7 @@ pub struct BarChart { pub(super) element_formatter: Option String>>, highlight: bool, + allow_hover: bool, id: Option, } @@ -1455,6 +1564,7 @@ impl BarChart { name: String::new(), element_formatter: None, highlight: false, + allow_hover: true, id: None, } } @@ -1523,6 +1633,13 @@ impl BarChart { self } + /// Allowed hovering this item in the plot. Default: `true`. + #[inline] + pub fn allow_hover(mut self, hovering: bool) -> Self { + self.allow_hover = hovering; + self + } + /// Add a custom way to format an element. /// Can be used to display a set number of decimals or custom labels. #[inline] @@ -1591,6 +1708,10 @@ impl PlotItem for BarChart { self.highlight } + fn allow_hover(&self) -> bool { + self.allow_hover + } + fn geometry(&self) -> PlotGeometry<'_> { PlotGeometry::Rects } @@ -1636,6 +1757,7 @@ pub struct BoxPlot { pub(super) element_formatter: Option String>>, highlight: bool, + allow_hover: bool, id: Option, } @@ -1648,6 +1770,7 @@ impl BoxPlot { name: String::new(), element_formatter: None, highlight: false, + allow_hover: true, id: None, } } @@ -1709,6 +1832,13 @@ impl BoxPlot { self } + /// Allowed hovering this item in the plot. Default: `true`. + #[inline] + pub fn allow_hover(mut self, hovering: bool) -> Self { + self.allow_hover = hovering; + self + } + /// Add a custom way to format an element. /// Can be used to display a set number of decimals or custom labels. #[inline] @@ -1752,6 +1882,10 @@ impl PlotItem for BoxPlot { self.highlight } + fn allow_hover(&self) -> bool { + self.allow_hover + } + fn geometry(&self) -> PlotGeometry<'_> { PlotGeometry::Rects } diff --git a/crates/egui_plot/src/lib.rs b/crates/egui_plot/src/lib.rs index e9962c843d2..26567117652 100644 --- a/crates/egui_plot/src/lib.rs +++ b/crates/egui_plot/src/lib.rs @@ -1649,12 +1649,15 @@ impl PreparedPlot { let interact_radius_sq = (16.0_f32).powi(2); - let candidates = items.iter().filter_map(|item| { - let item = &**item; - let closest = item.find_closest(pointer, transform); - - Some(item).zip(closest) - }); + let candidates = items + .iter() + .filter(|entry| entry.allow_hover()) + .filter_map(|item| { + let item = &**item; + let closest = item.find_closest(pointer, transform); + + Some(item).zip(closest) + }); let closest = candidates .min_by_key(|(_, elem)| elem.dist_sq.ord()) From 58a27882b0d6d86168dde9c952d2b9b5025a3c68 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 27 Mar 2024 16:39:06 +0100 Subject: [PATCH 050/134] Fix touch-and-hold to open context menu (#4249) This was broken in cases where the ui wasn't waking up, i.e. when nothing else was happening. --- crates/egui/src/input_state.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/egui/src/input_state.rs b/crates/egui/src/input_state.rs index a9c1b314383..9c6103e0a54 100644 --- a/crates/egui/src/input_state.rs +++ b/crates/egui/src/input_state.rs @@ -326,6 +326,10 @@ impl InputState { self.pointer.wants_repaint() || self.unprocessed_scroll_delta.abs().max_elem() > 0.2 || !self.events.is_empty() + + // We need to wake up and check for press-and-hold for the context menu. + // TODO(emilk): wake up after `MAX_CLICK_DURATION` instead of every frame. + || (self.any_touches() && !self.pointer.is_decidedly_dragging()) } /// Count presses of a key. If non-zero, the presses are consumed, so that this will only return non-zero once. From e183655aac76b9f76bdb4c82786dfacf53c0c09d Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 28 Mar 2024 10:09:21 +0100 Subject: [PATCH 051/134] Don't apply a clip rect to the contents of an `Area` or `Window` (#4258) The edges were rather arbitrarily chosen anyway, and I'm not sure who it was supposed to help. --- crates/egui/src/containers/area.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/crates/egui/src/containers/area.rs b/crates/egui/src/containers/area.rs index 0c73ddf9fc5..5cd0f01dfce 100644 --- a/crates/egui/src/containers/area.rs +++ b/crates/egui/src/containers/area.rs @@ -384,7 +384,7 @@ impl Area { let layer_id = LayerId::new(self.order, self.id); let area_rect = ctx.memory(|mem| mem.areas().get(self.id).map(|area| area.rect())); if let Some(area_rect) = area_rect { - let clip_rect = ctx.available_rect(); + let clip_rect = Rect::EVERYTHING; let painter = Painter::new(ctx.clone(), layer_id, clip_rect); // shrinkage: looks kinda a bad on its own @@ -437,12 +437,7 @@ impl Prepared { .at_least(self.state.left_top_pos() + Vec2::splat(32.0)), ); - let shadow_radius = ctx.style().visuals.window_shadow.margin().sum().max_elem(); // hacky - let clip_rect_margin = ctx.style().visuals.clip_rect_margin.max(shadow_radius); - - let clip_rect = Rect::from_min_max(self.state.left_top_pos(), constrain_rect.max) - .expand(clip_rect_margin) - .intersect(constrain_rect); + let clip_rect = constrain_rect; // Don't paint outside our bounds let mut ui = Ui::new( ctx.clone(), From 3dba73e63eb145ac3a6e5baffeefb79292133c62 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 28 Mar 2024 10:09:28 +0100 Subject: [PATCH 052/134] Improve the UI for changing the egui theme (#4257) I added a new demo - a `Frame` editor: ![frame-editor](https://github.com/emilk/egui/assets/1148717/d0bec169-c211-45a3-9f53-5059fb8fc224) This whole menu is now just a a bit nicer to use: Screenshot 2024-03-28 at 09 49 16 --- crates/egui/src/containers/frame.rs | 1 + crates/egui/src/context.rs | 2 +- crates/egui/src/introspection.rs | 44 +- crates/egui/src/lib.rs | 2 +- crates/egui/src/memory.rs | 8 +- crates/egui/src/style.rs | 705 ++++++++++++------ crates/egui/src/widgets/mod.rs | 67 +- .../egui_demo_app/src/apps/fractal_clock.rs | 2 +- crates/egui_demo_app/src/wrap_app.rs | 8 +- .../src/demo/demo_app_windows.rs | 1 + crates/egui_demo_lib/src/demo/frame_demo.rs | 73 ++ .../src/demo/misc_demo_window.rs | 2 +- crates/egui_demo_lib/src/demo/mod.rs | 1 + crates/egui_demo_lib/src/demo/paint_bezier.rs | 28 +- crates/egui_demo_lib/src/demo/painting.rs | 3 +- crates/egui_demo_lib/src/demo/plot_demo.rs | 2 +- crates/egui_demo_lib/src/demo/scrolling.rs | 2 +- crates/egui_demo_lib/src/demo/sliders.rs | 2 +- crates/egui_demo_lib/src/demo/strip_demo.rs | 2 +- crates/egui_demo_lib/src/demo/table_demo.rs | 2 +- crates/egui_demo_lib/src/demo/tests.rs | 4 +- .../egui_demo_lib/src/demo/window_options.rs | 2 +- .../src/easy_mark/easy_mark_editor.rs | 2 +- 23 files changed, 633 insertions(+), 332 deletions(-) create mode 100644 crates/egui_demo_lib/src/demo/frame_demo.rs diff --git a/crates/egui/src/containers/frame.rs b/crates/egui/src/containers/frame.rs index 92113f4dd49..2847fdfd1c5 100644 --- a/crates/egui/src/containers/frame.rs +++ b/crates/egui/src/containers/frame.rs @@ -52,6 +52,7 @@ use epaint::*; /// Note that you cannot change the margins after calling `begin`. #[doc(alias = "border")] #[derive(Clone, Copy, Debug, Default, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[must_use = "You should call .show()"] pub struct Frame { /// Margin within the painted frame. diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 376c41d94d4..222f8ab0575 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -1567,7 +1567,7 @@ impl Context { /// The [`Style`] used by all new windows, panels etc. /// - /// You can also change this using [`Self::style_mut]` + /// You can also change this using [`Self::style_mut`] /// /// You can use [`Ui::style_mut`] to change the style of a single [`Ui`]. pub fn set_style(&self, style: impl Into>) { diff --git a/crates/egui/src/introspection.rs b/crates/egui/src/introspection.rs index 9c738153873..f71e82194be 100644 --- a/crates/egui/src/introspection.rs +++ b/crates/egui/src/introspection.rs @@ -150,23 +150,33 @@ impl Widget for &mut epaint::TessellationOptions { validate_meshes, } = self; - ui.checkbox(feathering, "Feathering (antialias)") - .on_hover_text("Apply feathering to smooth out the edges of shapes. Turn off for small performance gain."); - let feathering_slider = crate::Slider::new(feathering_size_in_pixels, 0.0..=10.0) - .smallest_positive(0.1) - .logarithmic(true) - .text("Feathering size in pixels"); - ui.add_enabled(*feathering, feathering_slider); + ui.horizontal(|ui| { + ui.checkbox(feathering, "Feathering (antialias)") + .on_hover_text("Apply feathering to smooth out the edges of shapes. Turn off for small performance gain."); + + if *feathering { + ui.add(crate::DragValue::new(feathering_size_in_pixels).clamp_range(0.0..=10.0).speed(0.1).suffix(" px")); + } + }); ui.checkbox(prerasterized_discs, "Speed up filled circles with pre-rasterization"); - ui.add( - crate::widgets::Slider::new(bezier_tolerance, 0.0001..=10.0) - .logarithmic(true) - .show_value(true) - .text("Spline Tolerance"), - ); - ui.collapsing("debug", |ui| { + ui.horizontal(|ui| { + ui.label("Spline tolerance"); + let speed = 0.01 * *bezier_tolerance; + ui.add( + crate::DragValue::new(bezier_tolerance).clamp_range(0.0001..=10.0) + .speed(speed) + ); + }); + + ui.add_enabled(epaint::HAS_RAYON, crate::Checkbox::new(parallel_tessellation, "Parallelize tessellation") + ).on_hover_text("Only available if epaint was compiled with the rayon feature") + .on_disabled_hover_text("epaint was not compiled with the rayon feature"); + + ui.checkbox(validate_meshes, "Validate meshes").on_hover_text("Check that incoming meshes are valid, i.e. that all indices are in range, etc."); + + ui.collapsing("Debug", |ui| { ui.checkbox( coarse_tessellation_culling, "Do coarse culling in the tessellator", @@ -178,12 +188,6 @@ impl Widget for &mut epaint::TessellationOptions { ui.checkbox(debug_paint_clip_rects, "Paint clip rectangles"); ui.checkbox(debug_paint_text_rects, "Paint text bounds"); }); - - ui.add_enabled(epaint::HAS_RAYON, crate::Checkbox::new(parallel_tessellation, "Parallelize tessellation") - ).on_hover_text("Only available if epaint was compiled with the rayon feature") - .on_disabled_hover_text("epaint was not compiled with the rayon feature"); - - ui.checkbox(validate_meshes, "Validate meshes").on_hover_text("Check that incoming meshes are valid, i.e. that all indices are in range, etc."); }) .response } diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index e64668ec807..b8f0c8fa3d2 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -431,7 +431,7 @@ pub use epaint::{ text::{FontData, FontDefinitions, FontFamily, FontId, FontTweak}, textures::{TextureFilter, TextureOptions, TextureWrapMode, TexturesDelta}, ClippedPrimitive, ColorImage, FontImage, ImageData, Margin, Mesh, PaintCallback, - PaintCallbackInfo, Rounding, Shape, Stroke, TextureHandle, TextureId, + PaintCallbackInfo, Rounding, Shadow, Shape, Stroke, TextureHandle, TextureId, }; pub mod text { diff --git a/crates/egui/src/memory.rs b/crates/egui/src/memory.rs index dc611787fff..b8430828372 100644 --- a/crates/egui/src/memory.rs +++ b/crates/egui/src/memory.rs @@ -278,13 +278,15 @@ impl Options { }); CollapsingHeader::new("✒ Painting") - .default_open(true) + .default_open(false) .show(ui, |ui| { tessellation_options.ui(ui); - ui.vertical_centered(|ui| crate::reset_button(ui, tessellation_options)); + ui.vertical_centered(|ui| { + crate::reset_button(ui, tessellation_options, "Reset paint settings"); + }); }); - ui.vertical_centered(|ui| crate::reset_button(ui, self)); + ui.vertical_centered(|ui| crate::reset_button(ui, self, "Reset all")); } } diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index 5fc3b39ecf3..1523345f9b8 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -7,8 +7,8 @@ use std::collections::BTreeMap; use epaint::{Rounding, Shadow, Stroke}; use crate::{ - ecolor::*, emath::*, ComboBox, CursorIcon, FontFamily, FontId, Margin, Response, RichText, - WidgetText, + ecolor::*, emath::*, ComboBox, CursorIcon, FontFamily, FontId, Grid, Margin, Response, + RichText, WidgetText, }; // ---------------------------------------------------------------------------- @@ -1305,19 +1305,21 @@ impl Style { visuals.light_dark_radio_buttons(ui); crate::Grid::new("_options").show(ui, |ui| { - ui.label("Override font id:"); - ui.horizontal(|ui| { - ui.radio_value(override_font_id, None, "None"); - if ui.radio(override_font_id.is_some(), "override").clicked() { - *override_font_id = Some(FontId::default()); - } + ui.label("Override font id"); + ui.vertical(|ui| { + ui.horizontal(|ui| { + ui.radio_value(override_font_id, None, "None"); + if ui.radio(override_font_id.is_some(), "override").clicked() { + *override_font_id = Some(FontId::default()); + } + }); if let Some(override_font_id) = override_font_id { crate::introspection::font_id_ui(ui, override_font_id); } }); ui.end_row(); - ui.label("Override text style:"); + ui.label("Override text style"); crate::ComboBox::from_id_source("Override text style") .selected_text(match override_text_style { None => "None".to_owned(), @@ -1334,7 +1336,7 @@ impl Style { }); ui.end_row(); - ui.label("Text style of DragValue:"); + ui.label("Text style of DragValue"); crate::ComboBox::from_id_source("drag_value_text_style") .selected_text(drag_value_text_style.to_string()) .show_ui(ui, |ui| { @@ -1347,10 +1349,11 @@ impl Style { }); ui.end_row(); - ui.label("Animation duration:"); + ui.label("Animation duration"); ui.add( - Slider::new(animation_time, 0.0..=1.0) - .clamp_to_range(true) + DragValue::new(animation_time) + .clamp_range(0.0..=1.0) + .speed(0.02) .suffix(" s"), ); ui.end_row(); @@ -1376,7 +1379,7 @@ impl Style { "If scrolling is enabled for only one direction, allow horizontal scrolling without pressing shift", ); - ui.vertical_centered(|ui| reset_button(ui, self)); + ui.vertical_centered(|ui| reset_button(ui, self, "Reset style")); } } @@ -1389,7 +1392,7 @@ fn text_styles_ui(ui: &mut Ui, text_styles: &mut BTreeMap) -> ui.end_row(); } }); - crate::reset_button_with(ui, text_styles, default_text_styles()); + crate::reset_button_with(ui, text_styles, "Reset text styles", default_text_styles()); }) .response } @@ -1418,72 +1421,85 @@ impl Spacing { scroll, } = self; - ui.add(slider_vec2(item_spacing, 0.0..=20.0, "Item spacing")); + Grid::new("spacing") + .num_columns(2) + .spacing([12.0, 8.0]) + .striped(true) + .show(ui, |ui| { + ui.label("Item spacing"); + ui.add(two_drag_values(item_spacing, 0.0..=20.0)); + ui.end_row(); - margin_ui(ui, "Window margin:", window_margin); - margin_ui(ui, "Menu margin:", menu_margin); + ui.label("Window margin"); + ui.add(window_margin); + ui.end_row(); - ui.add(slider_vec2(button_padding, 0.0..=20.0, "Button padding")); - ui.add(slider_vec2(interact_size, 4.0..=60.0, "Interact size")) - .on_hover_text("Minimum size of an interactive widget"); - ui.horizontal(|ui| { - ui.add(DragValue::new(indent).clamp_range(0.0..=100.0)); - ui.label("Indent"); - }); - ui.horizontal(|ui| { - ui.add(DragValue::new(slider_width).clamp_range(0.0..=1000.0)); - ui.label("Slider width"); - }); - ui.horizontal(|ui| { - ui.add(DragValue::new(slider_rail_height).clamp_range(0.0..=50.0)); - ui.label("Slider rail height"); - }); - ui.horizontal(|ui| { - ui.add(DragValue::new(combo_width).clamp_range(0.0..=1000.0)); - ui.label("ComboBox width"); - }); - ui.horizontal(|ui| { - ui.add(DragValue::new(text_edit_width).clamp_range(0.0..=1000.0)); - ui.label("TextEdit width"); - }); + ui.label("Menu margin"); + ui.add(menu_margin); + ui.end_row(); - ui.collapsing("Scroll Area", |ui| { - scroll.ui(ui); - }); + ui.label("Button padding"); + ui.add(two_drag_values(button_padding, 0.0..=20.0)); + ui.end_row(); - ui.horizontal(|ui| { - ui.label("Checkboxes etc:"); - ui.add( - DragValue::new(icon_width) - .prefix("outer icon width:") - .clamp_range(0.0..=60.0), - ); - ui.add( - DragValue::new(icon_width_inner) - .prefix("inner icon width:") - .clamp_range(0.0..=60.0), - ); - ui.add( - DragValue::new(icon_spacing) - .prefix("spacing:") - .clamp_range(0.0..=10.0), - ); - }); + ui.label("Interact size") + .on_hover_text("Minimum size of an interactive widget"); + ui.add(two_drag_values(interact_size, 4.0..=60.0)); + ui.end_row(); - ui.horizontal(|ui| { - ui.add(DragValue::new(tooltip_width).clamp_range(0.0..=1000.0)); - ui.label("Tooltip wrap width"); - }); + ui.label("Indent"); + ui.add(DragValue::new(indent).clamp_range(0.0..=100.0)); + ui.end_row(); - ui.horizontal(|ui| { - ui.add(DragValue::new(menu_width).clamp_range(0.0..=1000.0)); - ui.label("Default width of a menu"); - }); + ui.label("Slider width"); + ui.add(DragValue::new(slider_width).clamp_range(0.0..=1000.0)); + ui.end_row(); - ui.horizontal(|ui| { - ui.add(DragValue::new(menu_spacing).clamp_range(0.0..=10.0)); - ui.label("Horizontal spacing between menus"); - }); + ui.label("Slider rail height"); + ui.add(DragValue::new(slider_rail_height).clamp_range(0.0..=50.0)); + ui.end_row(); + + ui.label("ComboBox width"); + ui.add(DragValue::new(combo_width).clamp_range(0.0..=1000.0)); + ui.end_row(); + + ui.label("TextEdit width"); + ui.add(DragValue::new(text_edit_width).clamp_range(0.0..=1000.0)); + ui.end_row(); + + ui.label("Tooltip wrap width"); + ui.add(DragValue::new(tooltip_width).clamp_range(0.0..=1000.0)); + ui.end_row(); + + ui.label("Default menu width"); + ui.add(DragValue::new(menu_width).clamp_range(0.0..=1000.0)); + ui.end_row(); + + ui.label("Menu spacing") + .on_hover_text("Horizontal spacing between menus"); + ui.add(DragValue::new(menu_spacing).clamp_range(0.0..=10.0)); + ui.end_row(); + + ui.label("Checkboxes etc"); + ui.vertical(|ui| { + ui.add( + DragValue::new(icon_width) + .prefix("outer icon width:") + .clamp_range(0.0..=60.0), + ); + ui.add( + DragValue::new(icon_width_inner) + .prefix("inner icon width:") + .clamp_range(0.0..=60.0), + ); + ui.add( + DragValue::new(icon_spacing) + .prefix("spacing:") + .clamp_range(0.0..=10.0), + ); + }); + ui.end_row(); + }); ui.checkbox( indent_ends_with_horizontal_line, @@ -1495,57 +1511,12 @@ impl Spacing { ui.add(DragValue::new(combo_height).clamp_range(0.0..=1000.0)); }); - ui.vertical_centered(|ui| reset_button(ui, self)); - } -} - -fn margin_ui(ui: &mut Ui, text: &str, margin: &mut Margin) { - let margin_range = 0.0..=20.0; - - ui.horizontal(|ui| { - ui.label(text); - - let mut same = margin.is_same(); - ui.checkbox(&mut same, "Same"); - - if same { - let mut value = margin.left; - ui.add(DragValue::new(&mut value).clamp_range(margin_range.clone())); - *margin = Margin::same(value); - } else { - if margin.is_same() { - // HACK: prevent collapse: - margin.right = margin.left + 1.0; - margin.bottom = margin.left + 2.0; - margin.top = margin.left + 3.0; - } + ui.collapsing("Scroll Area", |ui| { + scroll.ui(ui); + }); - ui.add( - DragValue::new(&mut margin.left) - .clamp_range(margin_range.clone()) - .prefix("L: "), - ) - .on_hover_text("Left margin"); - ui.add( - DragValue::new(&mut margin.right) - .clamp_range(margin_range.clone()) - .prefix("R: "), - ) - .on_hover_text("Right margin"); - ui.add( - DragValue::new(&mut margin.top) - .clamp_range(margin_range.clone()) - .prefix("T: "), - ) - .on_hover_text("Top margin"); - ui.add( - DragValue::new(&mut margin.bottom) - .clamp_range(margin_range) - .prefix("B: "), - ) - .on_hover_text("Bottom margin"); - } - }); + ui.vertical_centered(|ui| reset_button(ui, self, "Reset spacing")); + } } impl Interaction { @@ -1559,21 +1530,42 @@ impl Interaction { selectable_labels, multi_widget_text_select, } = self; - ui.add(Slider::new(interact_radius, 0.0..=20.0).text("interact_radius")) - .on_hover_text("Interact with the closest widget within this radius."); - ui.add(Slider::new(resize_grab_radius_side, 0.0..=20.0).text("resize_grab_radius_side")); - ui.add( - Slider::new(resize_grab_radius_corner, 0.0..=20.0).text("resize_grab_radius_corner"), - ); + + ui.spacing_mut().item_spacing = vec2(12.0, 8.0); + + Grid::new("interaction") + .num_columns(2) + .striped(true) + .show(ui, |ui| { + ui.label("interact_radius") + .on_hover_text("Interact with the closest widget within this radius."); + ui.add(DragValue::new(interact_radius).clamp_range(0.0..=20.0)); + ui.end_row(); + + ui.label("resize_grab_radius_side").on_hover_text("Radius of the interactive area of the side of a window during drag-to-resize"); + ui.add(DragValue::new(resize_grab_radius_side).clamp_range(0.0..=20.0)); + ui.end_row(); + + ui.label("resize_grab_radius_corner").on_hover_text("Radius of the interactive area of the corner of a window during drag-to-resize."); + ui.add(DragValue::new(resize_grab_radius_corner).clamp_range(0.0..=20.0)); + ui.end_row(); + + ui.label("Tooltip delay").on_hover_text( + "Delay in seconds before showing tooltips after the mouse stops moving", + ); + ui.add( + DragValue::new(tooltip_delay) + .clamp_range(0.0..=1.0) + .speed(0.05) + .suffix(" s"), + ); + ui.end_row(); + }); + ui.checkbox( show_tooltips_only_when_still, "Only show tooltips if mouse is still", ); - ui.add( - Slider::new(tooltip_delay, 0.0..=1.0) - .suffix(" s") - .text("tooltip_delay"), - ); ui.horizontal(|ui| { ui.checkbox(selectable_labels, "Selectable text in labels"); @@ -1582,7 +1574,7 @@ impl Interaction { } }); - ui.vertical_centered(|ui| reset_button(ui, self)); + ui.vertical_centered(|ui| reset_button(ui, self, "Reset interaction settings")); } } @@ -1627,8 +1619,16 @@ impl Selection { pub fn ui(&mut self, ui: &mut crate::Ui) { let Self { bg_fill, stroke } = self; ui.label("Selectable labels"); - ui_color(ui, bg_fill, "background fill"); - stroke_ui(ui, stroke, "stroke"); + + Grid::new("selectiom").num_columns(2).show(ui, |ui| { + ui.label("Background fill"); + ui.color_edit_button_srgba(bg_fill); + ui.end_row(); + + ui.label("Stroke"); + ui.add(stroke); + ui.end_row(); + }); } } @@ -1642,17 +1642,39 @@ impl WidgetVisuals { fg_stroke, expansion, } = self; - ui_color(ui, weak_bg_fill, "optional background fill") - .on_hover_text("For buttons, combo-boxes, etc"); - ui_color(ui, mandatory_bg_fill, "mandatory background fill") - .on_hover_text("For checkboxes, sliders, etc"); - stroke_ui(ui, bg_stroke, "background stroke"); - rounding_ui(ui, rounding); + Grid::new("widget") + .num_columns(2) + .spacing([12.0, 8.0]) + .striped(true) + .show(ui, |ui| { + ui.label("Optional background fill") + .on_hover_text("For buttons, combo-boxes, etc"); + ui.color_edit_button_srgba(weak_bg_fill); + ui.end_row(); + + ui.label("Mandatory background fill") + .on_hover_text("For checkboxes, sliders, etc"); + ui.color_edit_button_srgba(mandatory_bg_fill); + ui.end_row(); + + ui.label("Background stroke"); + ui.add(bg_stroke); + ui.end_row(); - stroke_ui(ui, fg_stroke, "foreground stroke (text)"); - ui.add(Slider::new(expansion, -5.0..=5.0).text("expansion")) - .on_hover_text("make shapes this much larger"); + ui.label("Rounding"); + ui.add(rounding); + ui.end_row(); + + ui.label("Foreground stroke (text)"); + ui.add(fg_stroke); + ui.end_row(); + + ui.label("Expansion") + .on_hover_text("make shapes this much larger"); + ui.add(DragValue::new(expansion).speed(0.1)); + ui.end_row(); + }); } } @@ -1745,79 +1767,123 @@ impl Visuals { }); ui.collapsing("Window", |ui| { - ui_color(ui, window_fill, "Fill"); - stroke_ui(ui, window_stroke, "Outline"); - rounding_ui(ui, window_rounding); - shadow_ui(ui, window_shadow, "Shadow"); + Grid::new("window") + .num_columns(2) + .spacing([12.0, 8.0]) + .striped(true) + .show(ui, |ui| { + ui.label("Fill"); + ui.color_edit_button_srgba(window_fill); + ui.end_row(); + + ui.label("Stroke"); + ui.add(window_stroke); + ui.end_row(); + + ui.label("Rounding"); + ui.add(window_rounding); + ui.end_row(); + + ui.label("Shadow"); + ui.add(window_shadow); + ui.end_row(); + }); + ui.checkbox(window_highlight_topmost, "Highlight topmost Window"); }); ui.collapsing("Menus and popups", |ui| { - rounding_ui(ui, menu_rounding); - shadow_ui(ui, popup_shadow, "Shadow"); + Grid::new("menus_and_popups") + .num_columns(2) + .spacing([12.0, 8.0]) + .striped(true) + .show(ui, |ui| { + ui.label("Rounding"); + ui.add(menu_rounding); + ui.end_row(); + + ui.label("Shadow"); + ui.add(popup_shadow); + ui.end_row(); + }); }); ui.collapsing("Widgets", |ui| widgets.ui(ui)); ui.collapsing("Selection", |ui| selection.ui(ui)); - ui.horizontal(|ui| { - ui_color( - ui, - &mut widgets.noninteractive.fg_stroke.color, - "Text color", + ui.collapsing("Other colors", |ui| { + ui.horizontal(|ui| { + ui_color( + ui, + &mut widgets.noninteractive.fg_stroke.color, + "Text color", + ); + ui_color(ui, warn_fg_color, RichText::new("Warnings")); + ui_color(ui, error_fg_color, RichText::new("Errors")); + }); + + ui_color(ui, code_bg_color, RichText::new("Code background").code()).on_hover_ui( + |ui| { + ui.horizontal(|ui| { + ui.spacing_mut().item_spacing.x = 0.0; + ui.label("For monospaced inlined text "); + ui.code("like this"); + ui.label("."); + }); + }, ); - ui_color(ui, warn_fg_color, RichText::new("Warnings")); - ui_color(ui, error_fg_color, RichText::new("Errors")); - }); - ui_color(ui, code_bg_color, RichText::new("Code background").code()).on_hover_ui(|ui| { + ui_color(ui, hyperlink_color, "hyperlink_color"); + ui.horizontal(|ui| { - ui.spacing_mut().item_spacing.x = 0.0; - ui.label("For monospaced inlined text "); - ui.code("like this"); - ui.label("."); + ui.label("Text cursor"); + ui.add(text_cursor); }); }); - ui_color(ui, hyperlink_color, "hyperlink_color"); - stroke_ui(ui, text_cursor, "Text Cursor"); + ui.collapsing("Misc", |ui| { + ui.add(Slider::new(resize_corner_size, 0.0..=20.0).text("resize_corner_size")); + ui.checkbox(text_cursor_preview, "Preview text cursor on hover"); + ui.add(Slider::new(clip_rect_margin, 0.0..=20.0).text("clip_rect_margin")); - ui.add(Slider::new(resize_corner_size, 0.0..=20.0).text("resize_corner_size")); - ui.checkbox(text_cursor_preview, "Preview text cursor on hover"); - ui.add(Slider::new(clip_rect_margin, 0.0..=20.0).text("clip_rect_margin")); - - ui.checkbox(button_frame, "Button has a frame"); - ui.checkbox(collapsing_header_frame, "Collapsing header has a frame"); - ui.checkbox( - indent_has_left_vline, - "Paint a vertical line to the left of indented regions", - ); + ui.checkbox(button_frame, "Button has a frame"); + ui.checkbox(collapsing_header_frame, "Collapsing header has a frame"); + ui.checkbox( + indent_has_left_vline, + "Paint a vertical line to the left of indented regions", + ); - ui.checkbox(striped, "By default, add stripes to grids and tables?"); + ui.checkbox(striped, "Default stripes on grids and tables"); - ui.checkbox(slider_trailing_fill, "Add trailing color to sliders"); + ui.checkbox(slider_trailing_fill, "Add trailing color to sliders"); - handle_shape.ui(ui); + handle_shape.ui(ui); - ComboBox::from_label("Interact Cursor") - .selected_text(format!("{interact_cursor:?}")) - .show_ui(ui, |ui| { - ui.selectable_value(interact_cursor, None, "None"); + ComboBox::from_label("Interact cursor") + .selected_text( + interact_cursor.map_or_else(|| "-".to_owned(), |cursor| format!("{cursor:?}")), + ) + .show_ui(ui, |ui| { + ui.selectable_value(interact_cursor, None, "-"); - for icon in CursorIcon::ALL { - ui.selectable_value(interact_cursor, Some(icon), format!("{icon:?}")); - } - }); + for cursor in CursorIcon::ALL { + ui.selectable_value(interact_cursor, Some(cursor), format!("{cursor:?}")) + .on_hover_cursor(cursor); + } + }) + .response + .on_hover_text("Use this cursor when hovering buttons etc"); - ui.checkbox(image_loading_spinners, "Image loading spinners") - .on_hover_text("Show a spinner when an Image is loading"); + ui.checkbox(image_loading_spinners, "Image loading spinners") + .on_hover_text("Show a spinner when an Image is loading"); - ui.horizontal(|ui| { - ui.label("Color picker type:"); - numeric_color_space.toggle_button_ui(ui); + ui.horizontal(|ui| { + ui.label("Color picker type"); + numeric_color_space.toggle_button_ui(ui); + }); }); - ui.vertical_centered(|ui| reset_button(ui, self)); + ui.vertical_centered(|ui| reset_button(ui, self, "Reset visuals")); } } @@ -1862,16 +1928,12 @@ impl DebugOptions { ui.checkbox(show_widget_hits, "Show widgets under mouse pointer"); - ui.vertical_centered(|ui| reset_button(ui, self)); + ui.vertical_centered(|ui| reset_button(ui, self, "Reset debug options")); } } -// TODO(emilk): improve and standardize `slider_vec2` -fn slider_vec2<'a>( - value: &'a mut Vec2, - range: std::ops::RangeInclusive, - text: &'a str, -) -> impl Widget + 'a { +// TODO(emilk): improve and standardize +fn two_drag_values(value: &mut Vec2, range: std::ops::RangeInclusive) -> impl Widget + '_ { move |ui: &mut crate::Ui| { ui.horizontal(|ui| { ui.add( @@ -1884,7 +1946,6 @@ fn slider_vec2<'a>( .clamp_range(range.clone()) .prefix("y: "), ); - ui.label(text); }) .response } @@ -1898,36 +1959,10 @@ fn ui_color(ui: &mut Ui, srgba: &mut Color32, label: impl Into) -> R .response } -fn rounding_ui(ui: &mut Ui, rounding: &mut Rounding) { - const MAX: f32 = 20.0; - let mut same = rounding.is_same(); - ui.group(|ui| { - ui.horizontal(|ui| { - ui.label("Rounding: "); - ui.radio_value(&mut same, true, "Same"); - ui.radio_value(&mut same, false, "Separate"); - }); - - if same { - let mut cr = rounding.nw; - ui.add(Slider::new(&mut cr, 0.0..=MAX)); - *rounding = Rounding::same(cr); - } else { - ui.add(Slider::new(&mut rounding.nw, 0.0..=MAX).text("North-West")); - ui.add(Slider::new(&mut rounding.ne, 0.0..=MAX).text("North-East")); - ui.add(Slider::new(&mut rounding.sw, 0.0..=MAX).text("South-West")); - ui.add(Slider::new(&mut rounding.se, 0.0..=MAX).text("South-East")); - if rounding.is_same() { - rounding.se *= 1.00001; - } - } - }); -} - impl HandleShape { pub fn ui(&mut self, ui: &mut Ui) { - ui.label("Widget handle shape"); ui.horizontal(|ui| { + ui.label("Slider handle"); ui.radio_value(self, Self::Circle, "Circle"); if ui .radio(matches!(self, Self::Rect { .. }), "Rectangle") @@ -1983,3 +2018,213 @@ impl std::fmt::Display for NumericColorSpace { } } } + +impl Widget for &mut Margin { + fn ui(self, ui: &mut Ui) -> Response { + let mut same = self.is_same(); + + let response = if same { + ui.horizontal(|ui| { + ui.checkbox(&mut same, "same"); + + let mut value = self.left; + ui.add(DragValue::new(&mut value)); + *self = Margin::same(value); + }) + .response + } else { + ui.vertical(|ui| { + ui.checkbox(&mut same, "same"); + + crate::Grid::new("margin").num_columns(2).show(ui, |ui| { + ui.label("Left"); + ui.add(DragValue::new(&mut self.left)); + ui.end_row(); + + ui.label("Right"); + ui.add(DragValue::new(&mut self.right)); + ui.end_row(); + + ui.label("Top"); + ui.add(DragValue::new(&mut self.top)); + ui.end_row(); + + ui.label("Bottom"); + ui.add(DragValue::new(&mut self.bottom)); + ui.end_row(); + }); + }) + .response + }; + + // Apply the checkbox: + if same { + *self = Margin::same((self.left + self.right + self.top + self.bottom) / 4.0); + } else if self.is_same() { + self.right *= 1.00001; // prevent collapsing into sameness + } + + response + } +} + +impl Widget for &mut Rounding { + fn ui(self, ui: &mut Ui) -> Response { + let mut same = self.is_same(); + + let response = if same { + ui.horizontal(|ui| { + ui.checkbox(&mut same, "same"); + + let mut cr = self.nw; + ui.add(DragValue::new(&mut cr).clamp_range(0.0..=f32::INFINITY)); + *self = Rounding::same(cr); + }) + .response + } else { + ui.vertical(|ui| { + ui.checkbox(&mut same, "same"); + + crate::Grid::new("rounding").num_columns(2).show(ui, |ui| { + ui.label("NW"); + ui.add(DragValue::new(&mut self.nw).clamp_range(0.0..=f32::INFINITY)); + ui.end_row(); + + ui.label("NE"); + ui.add(DragValue::new(&mut self.ne).clamp_range(0.0..=f32::INFINITY)); + ui.end_row(); + + ui.label("SW"); + ui.add(DragValue::new(&mut self.sw).clamp_range(0.0..=f32::INFINITY)); + ui.end_row(); + + ui.label("SE"); + ui.add(DragValue::new(&mut self.se).clamp_range(0.0..=f32::INFINITY)); + ui.end_row(); + }); + }) + .response + }; + + // Apply the checkbox: + if same { + *self = Rounding::same((self.nw + self.ne + self.sw + self.se) / 4.0); + } else if self.is_same() { + self.se *= 1.00001; // prevent collapsing into sameness + } + + response + } +} + +impl Widget for &mut Shadow { + fn ui(self, ui: &mut Ui) -> Response { + let epaint::Shadow { + offset, + blur, + spread, + color, + } = self; + + ui.vertical(|ui| { + crate::Grid::new("shadow_ui").show(ui, |ui| { + ui.add( + DragValue::new(&mut offset.x) + .speed(1.0) + .clamp_range(-100.0..=100.0) + .prefix("x: "), + ); + ui.add( + DragValue::new(&mut offset.y) + .speed(1.0) + .clamp_range(-100.0..=100.0) + .prefix("y: "), + ); + ui.end_row(); + + ui.add( + DragValue::new(blur) + .speed(1.0) + .clamp_range(0.0..=100.0) + .prefix("blur: "), + ); + + ui.add( + DragValue::new(spread) + .speed(1.0) + .clamp_range(0.0..=100.0) + .prefix("spread: "), + ); + }); + ui.color_edit_button_srgba(color); + }) + .response + } +} + +impl Widget for &mut Stroke { + fn ui(self, ui: &mut Ui) -> Response { + let Stroke { width, color } = self; + + ui.horizontal(|ui| { + ui.add( + DragValue::new(width) + .speed(0.1) + .clamp_range(0.0..=f32::INFINITY), + ) + .on_hover_text("Width"); + ui.color_edit_button_srgba(color); + + // stroke preview: + let (_id, stroke_rect) = ui.allocate_space(ui.spacing().interact_size); + let left = stroke_rect.left_center(); + let right = stroke_rect.right_center(); + ui.painter().line_segment([left, right], (*width, *color)); + }) + .response + } +} + +impl Widget for &mut crate::Frame { + fn ui(self, ui: &mut Ui) -> Response { + let crate::Frame { + inner_margin, + outer_margin, + rounding, + shadow, + fill, + stroke, + } = self; + + crate::Grid::new("frame") + .num_columns(2) + .spacing([12.0, 8.0]) + .striped(true) + .show(ui, |ui| { + ui.label("Inner margin"); + ui.add(inner_margin); + ui.end_row(); + + ui.label("Outer margin"); + ui.add(outer_margin); + ui.end_row(); + + ui.label("Rounding"); + ui.add(rounding); + ui.end_row(); + + ui.label("Shadow"); + ui.add(shadow); + ui.end_row(); + + ui.label("Fill"); + ui.color_edit_button_srgba(fill); + ui.end_row(); + + ui.label("Stroke"); + ui.add(stroke); + ui.end_row(); + }) + .response + } +} diff --git a/crates/egui/src/widgets/mod.rs b/crates/egui/src/widgets/mod.rs index 77ee8692796..6b3fb72e0c5 100644 --- a/crates/egui/src/widgets/mod.rs +++ b/crates/egui/src/widgets/mod.rs @@ -92,15 +92,19 @@ pub trait WidgetWithState { /// Show a button to reset a value to its default. /// The button is only enabled if the value does not already have its original value. -pub fn reset_button(ui: &mut Ui, value: &mut T) { - reset_button_with(ui, value, T::default()); +/// +/// The `text` could be something like "Reset foo". +pub fn reset_button(ui: &mut Ui, value: &mut T, text: &str) { + reset_button_with(ui, value, text, T::default()); } /// Show a button to reset a value to its default. /// The button is only enabled if the value does not already have its original value. -pub fn reset_button_with(ui: &mut Ui, value: &mut T, reset_value: T) { +/// +/// The `text` could be something like "Reset foo". +pub fn reset_button_with(ui: &mut Ui, value: &mut T, text: &str, reset_value: T) { if ui - .add_enabled(*value != reset_value, Button::new("Reset")) + .add_enabled(*value != reset_value, Button::new(text)) .clicked() { *value = reset_value; @@ -109,62 +113,11 @@ pub fn reset_button_with(ui: &mut Ui, value: &mut T, reset_value: // ---------------------------------------------------------------------------- +#[deprecated = "Use `ui.add(&mut stroke)` instead"] pub fn stroke_ui(ui: &mut crate::Ui, stroke: &mut epaint::Stroke, text: &str) { - let epaint::Stroke { width, color } = stroke; ui.horizontal(|ui| { - ui.add(DragValue::new(width).speed(0.1).clamp_range(0.0..=5.0)) - .on_hover_text("Width"); - ui.color_edit_button_srgba(color); ui.label(text); - - // stroke preview: - let (_id, stroke_rect) = ui.allocate_space(ui.spacing().interact_size); - let left = stroke_rect.left_center(); - let right = stroke_rect.right_center(); - ui.painter().line_segment([left, right], (*width, *color)); - }); -} - -pub(crate) fn shadow_ui(ui: &mut Ui, shadow: &mut epaint::Shadow, text: &str) { - let epaint::Shadow { - offset, - blur, - spread, - color, - } = shadow; - - ui.label(text); - ui.indent(text, |ui| { - crate::Grid::new("shadow_ui").show(ui, |ui| { - ui.add( - DragValue::new(&mut offset.x) - .speed(1.0) - .clamp_range(-100.0..=100.0) - .prefix("x: "), - ); - ui.add( - DragValue::new(&mut offset.y) - .speed(1.0) - .clamp_range(-100.0..=100.0) - .prefix("y: "), - ); - ui.end_row(); - - ui.add( - DragValue::new(blur) - .speed(1.0) - .clamp_range(0.0..=100.0) - .prefix("Blur:"), - ); - - ui.add( - DragValue::new(spread) - .speed(1.0) - .clamp_range(0.0..=100.0) - .prefix("Spread:"), - ); - }); - ui.color_edit_button_srgba(color); + ui.add(stroke); }); } diff --git a/crates/egui_demo_app/src/apps/fractal_clock.rs b/crates/egui_demo_app/src/apps/fractal_clock.rs index 6afe6f0459e..1a6f4f55ebb 100644 --- a/crates/egui_demo_app/src/apps/fractal_clock.rs +++ b/crates/egui_demo_app/src/apps/fractal_clock.rs @@ -79,7 +79,7 @@ impl FractalClock { ui.add(Slider::new(&mut self.luminance_factor, 0.0..=1.0).text("luminance factor")); ui.add(Slider::new(&mut self.width_factor, 0.0..=1.0).text("width factor")); - egui::reset_button(ui, self); + egui::reset_button(ui, self, "Reset"); ui.hyperlink_to( "Inspired by a screensaver by Rob Mayoff", diff --git a/crates/egui_demo_app/src/wrap_app.rs b/crates/egui_demo_app/src/wrap_app.rs index e87814cb401..db3ee385d9b 100644 --- a/crates/egui_demo_app/src/wrap_app.rs +++ b/crates/egui_demo_app/src/wrap_app.rs @@ -245,7 +245,13 @@ impl eframe::App for WrapApp { } fn clear_color(&self, visuals: &egui::Visuals) -> [f32; 4] { - visuals.panel_fill.to_normalized_gamma_f32() + // Give the area behind the floating windows a different color, because it looks better: + let color = egui::lerp( + egui::Rgba::from(visuals.panel_fill)..=egui::Rgba::from(visuals.extreme_bg_color), + 0.5, + ); + let color = egui::Color32::from(color); + color.to_normalized_gamma_f32() } fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { diff --git a/crates/egui_demo_lib/src/demo/demo_app_windows.rs b/crates/egui_demo_lib/src/demo/demo_app_windows.rs index de1d96cddf4..505e938d0d8 100644 --- a/crates/egui_demo_lib/src/demo/demo_app_windows.rs +++ b/crates/egui_demo_lib/src/demo/demo_app_windows.rs @@ -29,6 +29,7 @@ impl Default for Demos { Box::::default(), Box::::default(), Box::::default(), + Box::::default(), Box::::default(), Box::::default(), Box::::default(), diff --git a/crates/egui_demo_lib/src/demo/frame_demo.rs b/crates/egui_demo_lib/src/demo/frame_demo.rs new file mode 100644 index 00000000000..b4013c93bce --- /dev/null +++ b/crates/egui_demo_lib/src/demo/frame_demo.rs @@ -0,0 +1,73 @@ +/// Shows off a table with dynamic layout +#[derive(PartialEq)] +pub struct FrameDemo { + frame: egui::Frame, +} + +impl Default for FrameDemo { + fn default() -> Self { + Self { + frame: egui::Frame { + inner_margin: 12.0.into(), + outer_margin: 24.0.into(), + rounding: 14.0.into(), + shadow: egui::Shadow { + offset: [8.0, 12.0].into(), + blur: 16.0, + spread: 0.0, + color: egui::Color32::from_black_alpha(180), + }, + fill: egui::Color32::from_rgba_unmultiplied(97, 0, 255, 128), + stroke: egui::Stroke::new(1.0, egui::Color32::GRAY), + }, + } + } +} + +impl super::Demo for FrameDemo { + fn name(&self) -> &'static str { + "▣ Frame" + } + + fn show(&mut self, ctx: &egui::Context, open: &mut bool) { + egui::Window::new(self.name()) + .open(open) + .resizable(false) + .show(ctx, |ui| { + use super::View as _; + self.ui(ui); + }); + } +} + +impl super::View for FrameDemo { + fn ui(&mut self, ui: &mut egui::Ui) { + ui.horizontal(|ui| { + ui.vertical(|ui| { + ui.add(&mut self.frame); + + ui.add_space(8.0); + ui.set_max_width(ui.min_size().x); + ui.vertical_centered(|ui| egui::reset_button(ui, self, "Reset")); + }); + + ui.separator(); + + ui.vertical(|ui| { + // We want to paint a background around the outer margin of the demonstration frame, so we use another frame around it: + egui::Frame::default() + .stroke(ui.visuals().widgets.noninteractive.bg_stroke) + .rounding(ui.visuals().widgets.noninteractive.rounding) + .show(ui, |ui| { + self.frame.show(ui, |ui| { + ui.label(egui::RichText::new("Content").color(egui::Color32::WHITE)); + }); + }); + }); + }); + + ui.set_max_width(ui.min_size().x); + ui.separator(); + ui.vertical_centered(|ui| ui.add(crate::egui_github_link_file!())); + } +} diff --git a/crates/egui_demo_lib/src/demo/misc_demo_window.rs b/crates/egui_demo_lib/src/demo/misc_demo_window.rs index b4b28511b62..f3c7a8dcfec 100644 --- a/crates/egui_demo_lib/src/demo/misc_demo_window.rs +++ b/crates/egui_demo_lib/src/demo/misc_demo_window.rs @@ -328,7 +328,7 @@ impl Default for ColorWidgets { impl ColorWidgets { fn ui(&mut self, ui: &mut Ui) { - egui::reset_button(ui, self); + egui::reset_button(ui, self, "Reset"); ui.label("egui lets you edit colors stored as either sRGBA or linear RGBA and with or without premultiplied alpha"); diff --git a/crates/egui_demo_lib/src/demo/mod.rs b/crates/egui_demo_lib/src/demo/mod.rs index d54498bbcf9..8a911c9d68b 100644 --- a/crates/egui_demo_lib/src/demo/mod.rs +++ b/crates/egui_demo_lib/src/demo/mod.rs @@ -13,6 +13,7 @@ pub mod demo_app_windows; pub mod drag_and_drop; pub mod extra_viewport; pub mod font_book; +pub mod frame_demo; pub mod highlighting; pub mod layout_test; pub mod misc_demo_window; diff --git a/crates/egui_demo_lib/src/demo/paint_bezier.rs b/crates/egui_demo_lib/src/demo/paint_bezier.rs index ad507de50a4..99d477b44ae 100644 --- a/crates/egui_demo_lib/src/demo/paint_bezier.rs +++ b/crates/egui_demo_lib/src/demo/paint_bezier.rs @@ -43,13 +43,27 @@ impl Default for PaintBezier { impl PaintBezier { pub fn ui_control(&mut self, ui: &mut egui::Ui) { ui.collapsing("Colors", |ui| { - ui.horizontal(|ui| { - ui.label("Fill color:"); - ui.color_edit_button_srgba(&mut self.fill); - }); - egui::stroke_ui(ui, &mut self.stroke, "Curve Stroke"); - egui::stroke_ui(ui, &mut self.aux_stroke, "Auxiliary Stroke"); - egui::stroke_ui(ui, &mut self.bounding_box_stroke, "Bounding Box Stroke"); + Grid::new("colors") + .num_columns(2) + .spacing([12.0, 8.0]) + .striped(true) + .show(ui, |ui| { + ui.label("Fill color"); + ui.color_edit_button_srgba(&mut self.fill); + ui.end_row(); + + ui.label("Curve Stroke"); + ui.add(&mut self.stroke); + ui.end_row(); + + ui.label("Auxiliary Stroke"); + ui.add(&mut self.aux_stroke); + ui.end_row(); + + ui.label("Bounding Box Stroke"); + ui.add(&mut self.bounding_box_stroke); + ui.end_row(); + }); }); ui.collapsing("Global tessellation options", |ui| { diff --git a/crates/egui_demo_lib/src/demo/painting.rs b/crates/egui_demo_lib/src/demo/painting.rs index 57e5f114da4..d95e8534653 100644 --- a/crates/egui_demo_lib/src/demo/painting.rs +++ b/crates/egui_demo_lib/src/demo/painting.rs @@ -20,7 +20,8 @@ impl Default for Painting { impl Painting { pub fn ui_control(&mut self, ui: &mut egui::Ui) -> egui::Response { ui.horizontal(|ui| { - egui::stroke_ui(ui, &mut self.stroke, "Stroke"); + ui.label("Stroke:"); + ui.add(&mut self.stroke); ui.separator(); if ui.button("Clear Painting").clicked() { self.lines.clear(); diff --git a/crates/egui_demo_lib/src/demo/plot_demo.rs b/crates/egui_demo_lib/src/demo/plot_demo.rs index 0f8b8edcc13..12e45aa0f09 100644 --- a/crates/egui_demo_lib/src/demo/plot_demo.rs +++ b/crates/egui_demo_lib/src/demo/plot_demo.rs @@ -62,7 +62,7 @@ impl super::Demo for PlotDemo { impl super::View for PlotDemo { fn ui(&mut self, ui: &mut Ui) { ui.horizontal(|ui| { - egui::reset_button(ui, self); + egui::reset_button(ui, self, "Reset"); ui.collapsing("Instructions", |ui| { ui.label("Pan by dragging, or scroll (+ shift = horizontal)."); ui.label("Box zooming: Right click to zoom in and zoom out using a selection."); diff --git a/crates/egui_demo_lib/src/demo/scrolling.rs b/crates/egui_demo_lib/src/demo/scrolling.rs index 0530f2fee99..9c490d23d8a 100644 --- a/crates/egui_demo_lib/src/demo/scrolling.rs +++ b/crates/egui_demo_lib/src/demo/scrolling.rs @@ -336,7 +336,7 @@ impl super::View for ScrollTo { ui.separator(); ui.vertical_centered(|ui| { - egui::reset_button(ui, self); + egui::reset_button(ui, self, "Reset"); ui.add(crate::egui_github_link_file!()); }); } diff --git a/crates/egui_demo_lib/src/demo/sliders.rs b/crates/egui_demo_lib/src/demo/sliders.rs index d2b065403c5..4142c7af0e1 100644 --- a/crates/egui_demo_lib/src/demo/sliders.rs +++ b/crates/egui_demo_lib/src/demo/sliders.rs @@ -198,7 +198,7 @@ impl super::View for Sliders { ui.add_space(8.0); ui.vertical_centered(|ui| { - egui::reset_button(ui, self); + egui::reset_button(ui, self, "Reset"); ui.add(crate::egui_github_link_file!()); }); } diff --git a/crates/egui_demo_lib/src/demo/strip_demo.rs b/crates/egui_demo_lib/src/demo/strip_demo.rs index defeac8275a..091d4f01dac 100644 --- a/crates/egui_demo_lib/src/demo/strip_demo.rs +++ b/crates/egui_demo_lib/src/demo/strip_demo.rs @@ -8,7 +8,7 @@ pub struct StripDemo {} impl super::Demo for StripDemo { fn name(&self) -> &'static str { - "▣ Strip Demo" + "▣ Strip" } fn show(&mut self, ctx: &egui::Context, open: &mut bool) { diff --git a/crates/egui_demo_lib/src/demo/table_demo.rs b/crates/egui_demo_lib/src/demo/table_demo.rs index f15d2df9e86..4cf0449fb6f 100644 --- a/crates/egui_demo_lib/src/demo/table_demo.rs +++ b/crates/egui_demo_lib/src/demo/table_demo.rs @@ -40,7 +40,7 @@ impl Default for TableDemo { impl super::Demo for TableDemo { fn name(&self) -> &'static str { - "☰ Table Demo" + "☰ Table" } fn show(&mut self, ctx: &egui::Context, open: &mut bool) { diff --git a/crates/egui_demo_lib/src/demo/tests.rs b/crates/egui_demo_lib/src/demo/tests.rs index 8d45de1e15b..a29e37fe0bc 100644 --- a/crates/egui_demo_lib/src/demo/tests.rs +++ b/crates/egui_demo_lib/src/demo/tests.rs @@ -131,7 +131,7 @@ impl super::Demo for ManualLayoutTest { impl super::View for ManualLayoutTest { fn ui(&mut self, ui: &mut egui::Ui) { - egui::reset_button(ui, self); + egui::reset_button(ui, self, "Reset"); let Self { widget_offset, @@ -298,7 +298,7 @@ impl super::View for TableTest { }); ui.vertical_centered(|ui| { - egui::reset_button(ui, self); + egui::reset_button(ui, self, "Reset"); ui.add(crate::egui_github_link_file!()); }); } diff --git a/crates/egui_demo_lib/src/demo/window_options.rs b/crates/egui_demo_lib/src/demo/window_options.rs index baa6eac652e..54c77988e75 100644 --- a/crates/egui_demo_lib/src/demo/window_options.rs +++ b/crates/egui_demo_lib/src/demo/window_options.rs @@ -146,7 +146,7 @@ impl super::View for WindowOptions { if ui.button("Disable for 2 seconds").clicked() { self.disabled_time = ui.input(|i| i.time); } - egui::reset_button(ui, self); + egui::reset_button(ui, self, "Reset"); ui.add(crate::egui_github_link_file!()); }); } diff --git a/crates/egui_demo_lib/src/easy_mark/easy_mark_editor.rs b/crates/egui_demo_lib/src/easy_mark/easy_mark_editor.rs index 2a44d78f3a7..0b0bb9608a3 100644 --- a/crates/egui_demo_lib/src/easy_mark/easy_mark_editor.rs +++ b/crates/egui_demo_lib/src/easy_mark/easy_mark_editor.rs @@ -48,7 +48,7 @@ impl EasyMarkEditor { let _ = ui.button("Hotkeys").on_hover_ui(nested_hotkeys_ui); ui.checkbox(&mut self.show_rendered, "Show rendered"); ui.checkbox(&mut self.highlight_editor, "Highlight editor"); - egui::reset_button(ui, self); + egui::reset_button(ui, self, "Reset"); ui.end_row(); }); ui.separator(); From c4f16af721be7060662cc76f2bd7793e3b656217 Mon Sep 17 00:00:00 2001 From: YgorSouza <43298013+YgorSouza@users.noreply.github.com> Date: Thu, 28 Mar 2024 10:43:28 +0100 Subject: [PATCH 053/134] Prevent plot from resetting one axis while zooming/dragging the other (#4252) * Closes --- crates/egui_plot/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/egui_plot/src/lib.rs b/crates/egui_plot/src/lib.rs index 26567117652..6056f46bd4e 100644 --- a/crates/egui_plot/src/lib.rs +++ b/crates/egui_plot/src/lib.rs @@ -1021,7 +1021,7 @@ impl Plot { delta.y = 0.0; } mem.transform.translate_bounds(delta); - mem.auto_bounds = !allow_drag; + mem.auto_bounds = mem.auto_bounds.and(!allow_drag); } // Zooming @@ -1092,7 +1092,7 @@ impl Plot { } if zoom_factor != Vec2::splat(1.0) { mem.transform.zoom(zoom_factor, hover_pos); - mem.auto_bounds = !allow_zoom; + mem.auto_bounds = mem.auto_bounds.and(!allow_zoom); } } if allow_scroll.any() { From 60da4b4f65531bb78736c8f6471b55cc8febb1b1 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 29 Mar 2024 10:59:24 +0100 Subject: [PATCH 054/134] Web: repaint if the `#hash` in the URL changes (#4261) Fixes a problem in egui.rs where the back-button would not work to switch between the top-level tabs --- crates/eframe/src/web/events.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/eframe/src/web/events.rs b/crates/eframe/src/web/events.rs index 30775733e58..c6737f6e770 100644 --- a/crates/eframe/src/web/events.rs +++ b/crates/eframe/src/web/events.rs @@ -242,6 +242,7 @@ pub(crate) fn install_window_events(runner_ref: &WebRunner) -> Result<(), JsValu runner_ref.add_event_listener(&window, "hashchange", |_: web_sys::Event, runner| { // `epi::Frame::info(&self)` clones `epi::IntegrationInfo`, but we need to modify the original here runner.frame.info.web_info.location.hash = location_hash(); + runner.needs_repaint.repaint_asap(); // tell the user about the new hash })?; Ok(()) From 7cc98bd38e1d34ca9a2fbd306e92ba881857e3f4 Mon Sep 17 00:00:00 2001 From: Justus Dieckmann <45795270+justusdieckmann@users.noreply.github.com> Date: Fri, 29 Mar 2024 11:55:49 +0100 Subject: [PATCH 055/134] Add web support for `zoom_factor` (#4260) Before, when setting the `zoom_factor`, the website was already enlarged, but the zoom was ignored when calculating the logical window size and mouse position, causing an offset between the actual cursor and selected elements. That is addressed here --------- Co-authored-by: Emil Ernerfeldt --- crates/eframe/src/web/app_runner.rs | 6 ++++-- crates/eframe/src/web/events.rs | 24 ++++++++++++++++++------ crates/eframe/src/web/input.rs | 22 +++++++++++++++------- crates/eframe/src/web/mod.rs | 4 ++-- crates/egui/src/memory.rs | 6 ++++++ 5 files changed, 45 insertions(+), 17 deletions(-) diff --git a/crates/eframe/src/web/app_runner.rs b/crates/eframe/src/web/app_runner.rs index 31f6e79c01c..620fe86fd62 100644 --- a/crates/eframe/src/web/app_runner.rs +++ b/crates/eframe/src/web/app_runner.rs @@ -60,7 +60,9 @@ impl AppRunner { super::storage::load_memory(&egui_ctx); egui_ctx.options_mut(|o| { - // On web, the browser controls the zoom factor: + // On web by default egui follows the zoom factor of the browser, + // and lets the browser handle the zoom shortscuts. + // A user can still zoom egui separately by calling [`egui::Context::set_zoom_factor`]. o.zoom_with_keyboard = false; o.zoom_factor = 1.0; }); @@ -183,7 +185,7 @@ impl AppRunner { /// The result can be painted later with a call to [`Self::run_and_paint`] or [`Self::paint`]. pub fn logic(&mut self) { super::resize_canvas_to_screen_size(self.canvas(), self.web_options.max_size_points); - let canvas_size = super::canvas_size_in_points(self.canvas()); + let canvas_size = super::canvas_size_in_points(self.canvas(), self.egui_ctx()); let raw_input = self.input.new_frame(canvas_size); let full_output = self.egui_ctx.run(raw_input, |egui_ctx| { diff --git a/crates/eframe/src/web/events.rs b/crates/eframe/src/web/events.rs index c6737f6e770..224ae3ac0db 100644 --- a/crates/eframe/src/web/events.rs +++ b/crates/eframe/src/web/events.rs @@ -297,7 +297,7 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu let modifiers = modifiers_from_mouse_event(&event); runner.input.raw.modifiers = modifiers; if let Some(button) = button_from_mouse_event(&event) { - let pos = pos_from_mouse_event(runner.canvas(), &event); + let pos = pos_from_mouse_event(runner.canvas(), &event, runner.egui_ctx()); let modifiers = runner.input.raw.modifiers; runner.input.raw.events.push(egui::Event::PointerButton { pos, @@ -324,7 +324,7 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu |event: web_sys::MouseEvent, runner| { let modifiers = modifiers_from_mouse_event(&event); runner.input.raw.modifiers = modifiers; - let pos = pos_from_mouse_event(runner.canvas(), &event); + let pos = pos_from_mouse_event(runner.canvas(), &event, runner.egui_ctx()); runner.input.raw.events.push(egui::Event::PointerMoved(pos)); runner.needs_repaint.repaint_asap(); event.stop_propagation(); @@ -336,7 +336,7 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu let modifiers = modifiers_from_mouse_event(&event); runner.input.raw.modifiers = modifiers; if let Some(button) = button_from_mouse_event(&event) { - let pos = pos_from_mouse_event(runner.canvas(), &event); + let pos = pos_from_mouse_event(runner.canvas(), &event, runner.egui_ctx()); let modifiers = runner.input.raw.modifiers; runner.input.raw.events.push(egui::Event::PointerButton { pos, @@ -374,7 +374,12 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu "touchstart", |event: web_sys::TouchEvent, runner| { let mut latest_touch_pos_id = runner.input.latest_touch_pos_id; - let pos = pos_from_touch_event(runner.canvas(), &event, &mut latest_touch_pos_id); + let pos = pos_from_touch_event( + runner.canvas(), + &event, + &mut latest_touch_pos_id, + runner.egui_ctx(), + ); runner.input.latest_touch_pos_id = latest_touch_pos_id; runner.input.latest_touch_pos = Some(pos); let modifiers = runner.input.raw.modifiers; @@ -397,7 +402,12 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu "touchmove", |event: web_sys::TouchEvent, runner| { let mut latest_touch_pos_id = runner.input.latest_touch_pos_id; - let pos = pos_from_touch_event(runner.canvas(), &event, &mut latest_touch_pos_id); + let pos = pos_from_touch_event( + runner.canvas(), + &event, + &mut latest_touch_pos_id, + runner.egui_ctx(), + ); runner.input.latest_touch_pos_id = latest_touch_pos_id; runner.input.latest_touch_pos = Some(pos); runner.input.raw.events.push(egui::Event::PointerMoved(pos)); @@ -460,7 +470,9 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu }); let scroll_multiplier = match unit { - egui::MouseWheelUnit::Page => canvas_size_in_points(runner.canvas()).y, + egui::MouseWheelUnit::Page => { + canvas_size_in_points(runner.canvas(), runner.egui_ctx()).y + } egui::MouseWheelUnit::Line => { #[allow(clippy::let_and_return)] let points_per_scroll_line = 8.0; // Note that this is intentionally different from what we use in winit. diff --git a/crates/eframe/src/web/input.rs b/crates/eframe/src/web/input.rs index 4ed0227739a..361e8031d40 100644 --- a/crates/eframe/src/web/input.rs +++ b/crates/eframe/src/web/input.rs @@ -3,11 +3,13 @@ use super::{canvas_origin, AppRunner}; pub fn pos_from_mouse_event( canvas: &web_sys::HtmlCanvasElement, event: &web_sys::MouseEvent, + ctx: &egui::Context, ) -> egui::Pos2 { let rect = canvas.get_bounding_client_rect(); + let zoom_factor = ctx.zoom_factor(); egui::Pos2 { - x: event.client_x() as f32 - rect.left() as f32, - y: event.client_y() as f32 - rect.top() as f32, + x: (event.client_x() as f32 - rect.left() as f32) / zoom_factor, + y: (event.client_y() as f32 - rect.top() as f32) / zoom_factor, } } @@ -32,6 +34,7 @@ pub fn pos_from_touch_event( canvas: &web_sys::HtmlCanvasElement, event: &web_sys::TouchEvent, touch_id_for_pos: &mut Option, + egui_ctx: &egui::Context, ) -> egui::Pos2 { let touch_for_pos = if let Some(touch_id_for_pos) = touch_id_for_pos { // search for the touch we previously used for the position @@ -49,14 +52,19 @@ pub fn pos_from_touch_event( .or_else(|| event.touches().get(0)) .map_or(Default::default(), |touch| { *touch_id_for_pos = Some(egui::TouchId::from(touch.identifier())); - pos_from_touch(canvas_origin(canvas), &touch) + pos_from_touch(canvas_origin(canvas), &touch, egui_ctx) }) } -fn pos_from_touch(canvas_origin: egui::Pos2, touch: &web_sys::Touch) -> egui::Pos2 { +fn pos_from_touch( + canvas_origin: egui::Pos2, + touch: &web_sys::Touch, + egui_ctx: &egui::Context, +) -> egui::Pos2 { + let zoom_factor = egui_ctx.zoom_factor(); egui::Pos2 { - x: touch.page_x() as f32 - canvas_origin.x, - y: touch.page_y() as f32 - canvas_origin.y, + x: (touch.page_x() as f32 - canvas_origin.x) / zoom_factor, + y: (touch.page_y() as f32 - canvas_origin.y) / zoom_factor, } } @@ -68,7 +76,7 @@ pub fn push_touches(runner: &mut AppRunner, phase: egui::TouchPhase, event: &web device_id: egui::TouchDeviceId(0), id: egui::TouchId::from(touch.identifier()), phase, - pos: pos_from_touch(canvas_origin, &touch), + pos: pos_from_touch(canvas_origin, &touch, runner.egui_ctx()), force: Some(touch.force()), }); } diff --git a/crates/eframe/src/web/mod.rs b/crates/eframe/src/web/mod.rs index d88e94229fd..a322a530aa6 100644 --- a/crates/eframe/src/web/mod.rs +++ b/crates/eframe/src/web/mod.rs @@ -116,8 +116,8 @@ fn canvas_origin(canvas: &web_sys::HtmlCanvasElement) -> egui::Pos2 { egui::pos2(rect.left() as f32, rect.top() as f32) } -fn canvas_size_in_points(canvas: &web_sys::HtmlCanvasElement) -> egui::Vec2 { - let pixels_per_point = native_pixels_per_point(); +fn canvas_size_in_points(canvas: &web_sys::HtmlCanvasElement, ctx: &egui::Context) -> egui::Vec2 { + let pixels_per_point = ctx.pixels_per_point(); egui::vec2( canvas.width() as f32 / pixels_per_point, canvas.height() as f32 / pixels_per_point, diff --git a/crates/egui/src/memory.rs b/crates/egui/src/memory.rs index b8430828372..7d61acd965f 100644 --- a/crates/egui/src/memory.rs +++ b/crates/egui/src/memory.rs @@ -185,6 +185,12 @@ pub struct Options { /// presses Cmd+Plus, Cmd+Minus or Cmd+0, just like in a browser. /// /// This is `true` by default. + /// + /// On the web-backend of `eframe` this is set to false by default, + /// so that the zoom shortcuts are handled exclusively by the browser, + /// which will change the `native_pixels_per_point` (`devicePixelRatio`). + /// You can still zoom egui independently by calling [`crate::Context::set_zoom_factor`], + /// which will be applied on top of the browsers global zoom. #[cfg_attr(feature = "serde", serde(skip))] pub zoom_with_keyboard: bool, From 946bc888db2a19dc0545480f517b96bab3ee9f9f Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 29 Mar 2024 12:15:03 +0100 Subject: [PATCH 056/134] Hide shortcut text on zoom buttons if `zoom_with_keyboard` is false (#4262) --- crates/egui/src/gui_zoom.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/crates/egui/src/gui_zoom.rs b/crates/egui/src/gui_zoom.rs index c4b5deb5ed6..bde60f4321b 100644 --- a/crates/egui/src/gui_zoom.rs +++ b/crates/egui/src/gui_zoom.rs @@ -70,10 +70,20 @@ pub fn zoom_out(ctx: &Context) { /// /// This is meant to be called from within a menu (See [`Ui::menu_button`]). pub fn zoom_menu_buttons(ui: &mut Ui) { + fn button(ctx: &Context, text: &str, shortcut: &KeyboardShortcut) -> Button<'static> { + let btn = Button::new(text); + let zoom_with_keyboard = ctx.options(|o| o.zoom_with_keyboard); + if zoom_with_keyboard { + btn.shortcut_text(ctx.format_shortcut(shortcut)) + } else { + btn + } + } + if ui .add_enabled( ui.ctx().zoom_factor() < MAX_ZOOM_FACTOR, - Button::new("Zoom In").shortcut_text(ui.ctx().format_shortcut(&kb_shortcuts::ZOOM_IN)), + button(ui.ctx(), "Zoom In", &kb_shortcuts::ZOOM_IN), ) .clicked() { @@ -84,8 +94,7 @@ pub fn zoom_menu_buttons(ui: &mut Ui) { if ui .add_enabled( ui.ctx().zoom_factor() > MIN_ZOOM_FACTOR, - Button::new("Zoom Out") - .shortcut_text(ui.ctx().format_shortcut(&kb_shortcuts::ZOOM_OUT)), + button(ui.ctx(), "Zoom Out", &kb_shortcuts::ZOOM_OUT), ) .clicked() { @@ -96,8 +105,7 @@ pub fn zoom_menu_buttons(ui: &mut Ui) { if ui .add_enabled( ui.ctx().zoom_factor() != 1.0, - Button::new("Reset Zoom") - .shortcut_text(ui.ctx().format_shortcut(&kb_shortcuts::ZOOM_RESET)), + button(ui.ctx(), "Reset Zoom", &kb_shortcuts::ZOOM_RESET), ) .clicked() { From dfbe118ea47576cc0400899649850193515ae630 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 29 Mar 2024 13:12:26 +0100 Subject: [PATCH 057/134] Release 0.27.1 (#4264) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## egui changelog ### 🐛 Fixed * Fix visual glitch on the right side of highly rounded rectangles [#4244](https://github.com/emilk/egui/pull/4244) * Prevent visual glitch when shadow blur width is very high [#4245](https://github.com/emilk/egui/pull/4245) * Fix `InputState::any_touches` and add `InputState::has_touch_screen` [#4247](https://github.com/emilk/egui/pull/4247) * Fix `Context::repaint_causes` returning no causes [#4248](https://github.com/emilk/egui/pull/4248) * Fix touch-and-hold to open context menu [#4249](https://github.com/emilk/egui/pull/4249) * Hide shortcut text on zoom buttons if `zoom_with_keyboard` is false [#4262](https://github.com/emilk/egui/pull/4262) ### 🔧 Changed * Don't apply a clip rect to the contents of an `Area` or `Window` [#4258](https://github.com/emilk/egui/pull/4258) ## eframe changelog * Web: repaint if the `#hash` in the URL changes [#4261](https://github.com/emilk/egui/pull/4261) * Add web support for `zoom_factor` [#4260](https://github.com/emilk/egui/pull/4260) (thanks [@justusdieckmann](https://github.com/justusdieckmann)!) --------- Co-authored-by: Justus Dieckmann <45795270+justusdieckmann@users.noreply.github.com> --- CHANGELOG.md | 13 ++++++++ Cargo.lock | 24 +++++++-------- Cargo.toml | 24 +++++++-------- crates/ecolor/CHANGELOG.md | 4 +++ crates/eframe/CHANGELOG.md | 5 ++++ crates/egui-wgpu/CHANGELOG.md | 4 +++ crates/egui-winit/CHANGELOG.md | 4 +++ crates/egui_extras/CHANGELOG.md | 4 +++ crates/egui_glow/CHANGELOG.md | 4 +++ crates/egui_plot/CHANGELOG.md | 4 +++ crates/epaint/CHANGELOG.md | 5 ++++ scripts/generate_changelog.py | 53 ++++++++++++++++++++++----------- 12 files changed, 107 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 128eba907eb..764b01eeeb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,19 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.27.1 - 2024-03-29 +### 🐛 Fixed +* Fix visual glitch on the right side of highly rounded rectangles [#4244](https://github.com/emilk/egui/pull/4244) +* Prevent visual glitch when shadow blur width is very high [#4245](https://github.com/emilk/egui/pull/4245) +* Fix `InputState::any_touches` and add `InputState::has_touch_screen` [#4247](https://github.com/emilk/egui/pull/4247) +* Fix `Context::repaint_causes` returning no causes [#4248](https://github.com/emilk/egui/pull/4248) +* Fix touch-and-hold to open context menu [#4249](https://github.com/emilk/egui/pull/4249) +* Hide shortcut text on zoom buttons if `zoom_with_keyboard` is false [#4262](https://github.com/emilk/egui/pull/4262) + +### 🔧 Changed +* Don't apply a clip rect to the contents of an `Area` or `Window` [#4258](https://github.com/emilk/egui/pull/4258) + + ## 0.27.0 - 2024-03-26 - Nicer menus and new hit test logic The hit test logic (what is the user clicking on?) has been completely rewritten, and should now be much more accurate and helpful. The hit test and interaction logic is run at the start of the frame, using the widgets rects from the previous frame, but the latest mouse coordinates. diff --git a/Cargo.lock b/Cargo.lock index 2a517c7059f..0a72704b809 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1187,7 +1187,7 @@ checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" [[package]] name = "ecolor" -version = "0.27.0" +version = "0.27.1" dependencies = [ "bytemuck", "cint", @@ -1198,7 +1198,7 @@ dependencies = [ [[package]] name = "eframe" -version = "0.27.0" +version = "0.27.1" dependencies = [ "bytemuck", "cocoa", @@ -1236,7 +1236,7 @@ dependencies = [ [[package]] name = "egui" -version = "0.27.0" +version = "0.27.1" dependencies = [ "accesskit", "ahash", @@ -1252,7 +1252,7 @@ dependencies = [ [[package]] name = "egui-wgpu" -version = "0.27.0" +version = "0.27.1" dependencies = [ "bytemuck", "document-features", @@ -1269,7 +1269,7 @@ dependencies = [ [[package]] name = "egui-winit" -version = "0.27.0" +version = "0.27.1" dependencies = [ "accesskit_winit", "arboard", @@ -1287,7 +1287,7 @@ dependencies = [ [[package]] name = "egui_demo_app" -version = "0.27.0" +version = "0.27.1" dependencies = [ "bytemuck", "chrono", @@ -1312,7 +1312,7 @@ dependencies = [ [[package]] name = "egui_demo_lib" -version = "0.27.0" +version = "0.27.1" dependencies = [ "chrono", "criterion", @@ -1327,7 +1327,7 @@ dependencies = [ [[package]] name = "egui_extras" -version = "0.27.0" +version = "0.27.1" dependencies = [ "chrono", "document-features", @@ -1345,7 +1345,7 @@ dependencies = [ [[package]] name = "egui_glow" -version = "0.27.0" +version = "0.27.1" dependencies = [ "bytemuck", "document-features", @@ -1365,7 +1365,7 @@ dependencies = [ [[package]] name = "egui_plot" -version = "0.27.0" +version = "0.27.1" dependencies = [ "document-features", "egui", @@ -1394,7 +1394,7 @@ checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "emath" -version = "0.27.0" +version = "0.27.1" dependencies = [ "bytemuck", "document-features", @@ -1469,7 +1469,7 @@ dependencies = [ [[package]] name = "epaint" -version = "0.27.0" +version = "0.27.1" dependencies = [ "ab_glyph", "ahash", diff --git a/Cargo.toml b/Cargo.toml index 431bffcfcc9..bde03d7c8fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ members = [ edition = "2021" license = "MIT OR Apache-2.0" rust-version = "1.72" -version = "0.27.0" +version = "0.27.1" [profile.release] @@ -48,17 +48,17 @@ opt-level = 2 [workspace.dependencies] -emath = { version = "0.27.0", path = "crates/emath", default-features = false } -ecolor = { version = "0.27.0", path = "crates/ecolor", default-features = false } -epaint = { version = "0.27.0", path = "crates/epaint", default-features = false } -egui = { version = "0.27.0", path = "crates/egui", default-features = false } -egui_plot = { version = "0.27.0", path = "crates/egui_plot", default-features = false } -egui-winit = { version = "0.27.0", path = "crates/egui-winit", default-features = false } -egui_extras = { version = "0.27.0", path = "crates/egui_extras", default-features = false } -egui-wgpu = { version = "0.27.0", path = "crates/egui-wgpu", default-features = false } -egui_demo_lib = { version = "0.27.0", path = "crates/egui_demo_lib", default-features = false } -egui_glow = { version = "0.27.0", path = "crates/egui_glow", default-features = false } -eframe = { version = "0.27.0", path = "crates/eframe", default-features = false } +emath = { version = "0.27.1", path = "crates/emath", default-features = false } +ecolor = { version = "0.27.1", path = "crates/ecolor", default-features = false } +epaint = { version = "0.27.1", path = "crates/epaint", default-features = false } +egui = { version = "0.27.1", path = "crates/egui", default-features = false } +egui_plot = { version = "0.27.1", path = "crates/egui_plot", default-features = false } +egui-winit = { version = "0.27.1", path = "crates/egui-winit", default-features = false } +egui_extras = { version = "0.27.1", path = "crates/egui_extras", default-features = false } +egui-wgpu = { version = "0.27.1", path = "crates/egui-wgpu", default-features = false } +egui_demo_lib = { version = "0.27.1", path = "crates/egui_demo_lib", default-features = false } +egui_glow = { version = "0.27.1", path = "crates/egui_glow", default-features = false } +eframe = { version = "0.27.1", path = "crates/eframe", default-features = false } #TODO(emilk): make more things workspace dependencies ahash = { version = "0.8.6", default-features = false, features = [ diff --git a/crates/ecolor/CHANGELOG.md b/crates/ecolor/CHANGELOG.md index d21513d6c15..493d125e7f1 100644 --- a/crates/ecolor/CHANGELOG.md +++ b/crates/ecolor/CHANGELOG.md @@ -6,6 +6,10 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.27.1 - 2024-03-29 +* Nothing new + + ## 0.27.0 - 2024-03-26 * Nothing new diff --git a/crates/eframe/CHANGELOG.md b/crates/eframe/CHANGELOG.md index 3f3c3d689ff..e110500cb53 100644 --- a/crates/eframe/CHANGELOG.md +++ b/crates/eframe/CHANGELOG.md @@ -7,6 +7,11 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.27.1 - 2024-03-29 +* Web: repaint if the `#hash` in the URL changes [#4261](https://github.com/emilk/egui/pull/4261) +* Add web support for `zoom_factor` [#4260](https://github.com/emilk/egui/pull/4260) (thanks [@justusdieckmann](https://github.com/justusdieckmann)!) + + ## 0.27.0 - 2024-03-26 * Update to document-features 0.2.8 [#4003](https://github.com/emilk/egui/pull/4003) * Added `App::raw_input_hook` allows for the manipulation or filtering of raw input events [#4008](https://github.com/emilk/egui/pull/4008) (thanks [@varphone](https://github.com/varphone)!) diff --git a/crates/egui-wgpu/CHANGELOG.md b/crates/egui-wgpu/CHANGELOG.md index 92da1e975a0..9dea60d1e0a 100644 --- a/crates/egui-wgpu/CHANGELOG.md +++ b/crates/egui-wgpu/CHANGELOG.md @@ -6,6 +6,10 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.27.1 - 2024-03-29 +* Nothing new + + ## 0.27.0 - 2024-03-26 * Improve panic message in egui-wgpu when failing to create buffers [#3986](https://github.com/emilk/egui/pull/3986) diff --git a/crates/egui-winit/CHANGELOG.md b/crates/egui-winit/CHANGELOG.md index ff85f8b5dcb..52dd56e8fdd 100644 --- a/crates/egui-winit/CHANGELOG.md +++ b/crates/egui-winit/CHANGELOG.md @@ -5,6 +5,10 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.27.1 - 2024-03-29 +* Nothing new + + ## 0.27.0 - 2024-03-26 * Update memoffset to 0.9.0, arboard to 3.3.1, and remove egui_glow's needless dependency on pure_glow's deps [#4036](https://github.com/emilk/egui/pull/4036) (thanks [@Nopey](https://github.com/Nopey)!) * Don't clear modifier state on focus change [#4157](https://github.com/emilk/egui/pull/4157) (thanks [@ming08108](https://github.com/ming08108)!) diff --git a/crates/egui_extras/CHANGELOG.md b/crates/egui_extras/CHANGELOG.md index fd498f3773a..0980768aee2 100644 --- a/crates/egui_extras/CHANGELOG.md +++ b/crates/egui_extras/CHANGELOG.md @@ -5,6 +5,10 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.27.1 - 2024-03-29 +* Nothing new + + ## 0.27.0 - 2024-03-26 * Add scroll bar visibility option to `Table` widget [#3981](https://github.com/emilk/egui/pull/3981) (thanks [@richardhozak](https://github.com/richardhozak)!) * Update `ehttp` to 0.5 [#4055](https://github.com/emilk/egui/pull/4055) diff --git a/crates/egui_glow/CHANGELOG.md b/crates/egui_glow/CHANGELOG.md index 970fb87a2b2..f39d8a199c5 100644 --- a/crates/egui_glow/CHANGELOG.md +++ b/crates/egui_glow/CHANGELOG.md @@ -6,6 +6,10 @@ Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.27.1 - 2024-03-29 +* Nothing new + + ## 0.27.0 - 2024-03-26 * Add `sense` option to `Plot` [#4052](https://github.com/emilk/egui/pull/4052) (thanks [@AmesingFlank](https://github.com/AmesingFlank)!) * Plot widget - allow disabling scroll for x and y separately [#4051](https://github.com/emilk/egui/pull/4051) (thanks [@YgorSouza](https://github.com/YgorSouza)!) diff --git a/crates/epaint/CHANGELOG.md b/crates/epaint/CHANGELOG.md index 4e222f85563..b5fc33f6731 100644 --- a/crates/epaint/CHANGELOG.md +++ b/crates/epaint/CHANGELOG.md @@ -5,6 +5,11 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.27.1 - 2024-03-29 +* Fix visual glitch on the right side of highly rounded rectangles [#4244](https://github.com/emilk/egui/pull/4244) +* Prevent visual glitch when shadow blur width is very high [#4245](https://github.com/emilk/egui/pull/4245) + + ## 0.27.0 - 2024-03-26 * Add `ColorImage::from_gray_iter` [#3536](https://github.com/emilk/egui/pull/3536) (thanks [@wangxiaochuTHU](https://github.com/wangxiaochuTHU)!) * Convenience const fn for `Margin`, `Rounding` and `Shadow` [#4080](https://github.com/emilk/egui/pull/4080) (thanks [@0Qwel](https://github.com/0Qwel)!) diff --git a/scripts/generate_changelog.py b/scripts/generate_changelog.py index 17b44f49be5..6b0fc71eb85 100755 --- a/scripts/generate_changelog.py +++ b/scripts/generate_changelog.py @@ -115,18 +115,22 @@ def print_section(crate: str, items: List[str]) -> None: print() +def changelog_filepath(crate: str) -> str: + scripts_dirpath = os.path.dirname(os.path.realpath(__file__)) + if crate == "egui": + file_path = f"{scripts_dirpath}/../CHANGELOG.md" + else: + file_path = f"{scripts_dirpath}/../crates/{crate}/CHANGELOG.md" + return os.path.normpath(file_path) + + def add_to_changelog_file(crate: str, items: List[str], version: str) -> None: insert_text = f"\n## {version} - {date.today()}\n" for item in items: insert_text += f"* {item}\n" insert_text += "\n" - scripts_dirpath = os.path.dirname(os.path.realpath(__file__)) - if crate == "egui": - file_path = f"{scripts_dirpath}/../CHANGELOG.md" - else: - file_path = f"{scripts_dirpath}/../crates/{crate}/CHANGELOG.md" - file_path = os.path.normpath(file_path) + file_path = changelog_filepath(crate) with open(file_path, 'r') as file: content = file.read() @@ -151,6 +155,28 @@ def main() -> None: print("ERROR: --version is required when --write is used") sys.exit(1) + crate_names = [ + "ecolor", + "eframe", + "egui_extras", + "egui_plot", + "egui_glow", + "egui-wgpu", + "egui-winit", + "egui", + "epaint", + ] + + # We read all existing changelogs to remove duplicate entries. + # For instance: the PRs that were part of 0.27.2 would also show up in the diff for `0.27.0..HEAD` + # when its time for a 0.28 release. We can't do `0.27.2..HEAD` because we would miss PRs that were + # merged before in `0.27.0..0.27.2` that were not cherry-picked into `0.27.2`. + all_changelogs = "" + for crate in crate_names: + file_path = changelog_filepath(crate) + with open(file_path, 'r') as file: + all_changelogs += file.read() + repo = Repo(".") commits = list(repo.iter_commits(args.commit_range)) commits.reverse() # Most recent last @@ -167,17 +193,6 @@ def main() -> None: ignore_labels = ["CI", "dependencies"] - crate_names = [ - "ecolor", - "eframe", - "egui_extras", - "egui_plot", - "egui_glow", - "egui-wgpu", - "egui-winit", - "egui", - "epaint", - ] sections = {} unsorted_prs = [] unsorted_commits = [] @@ -193,6 +208,10 @@ def main() -> None: summary = f"{title} [{hexsha[:7]}](https://github.com/{OWNER}/{REPO}/commit/{hexsha})" unsorted_commits.append(summary) else: + if f"[#{pr_number}]" in all_changelogs: + print(f"Ignoring PR that is already in the changelog: #{pr_number}") + continue + # We prefer the PR title if available title = pr_info.pr_title if pr_info else title labels = pr_info.labels if pr_info else [] From 0a428f0887c93f44545ec280778b295e7aa577c8 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 29 Mar 2024 15:58:22 +0100 Subject: [PATCH 058/134] Improve docs of `ui.collapsing` See https://github.com/emilk/egui/issues/4265 --- crates/egui/src/ui.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 56db9e97f60..9ff05a267e8 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -1806,6 +1806,9 @@ impl Ui { } /// A [`CollapsingHeader`] that starts out collapsed. + /// + /// The name must be unique within the current parent, + /// or you need to use [`CollapsingHeader::id_source`]. pub fn collapsing( &mut self, heading: impl Into, From 73dbfd689b6d1cf67bec170f3690a1c16d44e368 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 29 Mar 2024 15:58:37 +0100 Subject: [PATCH 059/134] Don't wrap the text in the `Frame` demo --- crates/egui_demo_lib/src/demo/frame_demo.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/egui_demo_lib/src/demo/frame_demo.rs b/crates/egui_demo_lib/src/demo/frame_demo.rs index b4013c93bce..0027a98d9da 100644 --- a/crates/egui_demo_lib/src/demo/frame_demo.rs +++ b/crates/egui_demo_lib/src/demo/frame_demo.rs @@ -60,6 +60,7 @@ impl super::View for FrameDemo { .rounding(ui.visuals().widgets.noninteractive.rounding) .show(ui, |ui| { self.frame.show(ui, |ui| { + ui.style_mut().wrap = Some(false); ui.label(egui::RichText::new("Content").color(egui::Color32::WHITE)); }); }); From a541e021aa6f972d1eb6006505e02ba1c2923a3d Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 29 Mar 2024 20:29:42 +0100 Subject: [PATCH 060/134] Add `RectShape::blur_width` to implement shadows (#4267) This is mostly a refactor, but has some performance benefits: * We (re)use the same tessellator as for everything else, leading to less allocations * We cull shapes before rendering them Adding `RectShape::blur_width` means it can also be used for other effects, such as glow. --- crates/egui/src/containers/frame.rs | 5 ++-- crates/egui/src/widgets/image.rs | 1 + crates/egui/src/widgets/slider.rs | 10 ++----- crates/epaint/src/shadow.rs | 42 +++------------------------- crates/epaint/src/shape.rs | 26 ++++++++++++++++- crates/epaint/src/shape_transform.rs | 1 + crates/epaint/src/tessellator.rs | 28 ++++++++++++++++++- 7 files changed, 62 insertions(+), 51 deletions(-) diff --git a/crates/egui/src/containers/frame.rs b/crates/egui/src/containers/frame.rs index 2847fdfd1c5..6d7ba6e6274 100644 --- a/crates/egui/src/containers/frame.rs +++ b/crates/egui/src/containers/frame.rs @@ -291,9 +291,8 @@ impl Frame { if shadow == Default::default() { frame_shape } else { - let shadow = shadow.tessellate(outer_rect, rounding); - let shadow = Shape::Mesh(shadow); - Shape::Vec(vec![shadow, frame_shape]) + let shadow = shadow.as_shape(outer_rect, rounding); + Shape::Vec(vec![Shape::from(shadow), frame_shape]) } } } diff --git a/crates/egui/src/widgets/image.rs b/crates/egui/src/widgets/image.rs index 90176018cb7..945c497dd4e 100644 --- a/crates/egui/src/widgets/image.rs +++ b/crates/egui/src/widgets/image.rs @@ -762,6 +762,7 @@ pub fn paint_texture_at( rounding: options.rounding, fill: options.tint, stroke: Stroke::NONE, + blur_width: 0.0, fill_texture_id: texture.id, uv: options.uv, }); diff --git a/crates/egui/src/widgets/slider.rs b/crates/egui/src/widgets/slider.rs index c5c60b92610..a428fb7dc4d 100644 --- a/crates/egui/src/widgets/slider.rs +++ b/crates/egui/src/widgets/slider.rs @@ -739,14 +739,8 @@ impl<'a> Slider<'a> { }; let v = v + Vec2::splat(visuals.expansion); let rect = Rect::from_center_size(center, 2.0 * v); - ui.painter().add(epaint::RectShape { - fill: visuals.bg_fill, - stroke: visuals.fg_stroke, - rect, - rounding: visuals.rounding, - fill_texture_id: Default::default(), - uv: Rect::ZERO, - }); + ui.painter() + .rect(rect, visuals.rounding, visuals.bg_fill, visuals.fg_stroke); } } } diff --git a/crates/epaint/src/shadow.rs b/crates/epaint/src/shadow.rs index 8b73c07e0da..fc7de267b9a 100644 --- a/crates/epaint/src/shadow.rs +++ b/crates/epaint/src/shadow.rs @@ -1,5 +1,3 @@ -use emath::NumExt as _; - use super::*; /// The color and fuzziness of a fuzzy shape. @@ -37,11 +35,10 @@ impl Shadow { color: Color32::TRANSPARENT, }; - pub fn tessellate(&self, rect: Rect, rounding: impl Into) -> Mesh { + /// The argument is the rectangle of the shadow caster. + pub fn as_shape(&self, rect: Rect, rounding: impl Into) -> RectShape { // tessellator.clip_rect = clip_rect; // TODO(emilk): culling - use crate::tessellator::*; - let Self { offset, blur, @@ -50,40 +47,9 @@ impl Shadow { } = *self; let rect = rect.translate(offset).expand(spread); + let rounding = rounding.into() + Rounding::same(spread.abs()); - // We simulate a blurry shadow by tessellating a solid rectangle using a very large feathering. - // Feathering is usually used to make the edges of a shape softer for anti-aliasing. - // The tessellator can't handle blurring/feathering larger than the smallest side of the rect. - // Thats because the tessellator approximate very thin rectangles as line segments, - // and these line segments don't have rounded corners. - // When the feathering is small (the size of a pixel), this is usually fine, - // but here we have a huge feathering to simulate blur, - // so we need to avoid this optimization in the tessellator, - // which is also why we add this rather big epsilon: - let eps = 0.1; - let blur = blur.at_most(rect.size().min_elem() - eps).at_least(0.0); - - // TODO(emilk): if blur <= 0, return a simple `Shape::Rect` instead of using the tessellator - - let rounding_expansion = spread.abs() + 0.5 * blur; - let rounding = rounding.into() + Rounding::same(rounding_expansion); - - let rect = RectShape::filled(rect, rounding, color); - let pixels_per_point = 1.0; // doesn't matter here - let font_tex_size = [1; 2]; // unused since we are not tessellating text. - let mut tessellator = Tessellator::new( - pixels_per_point, - TessellationOptions { - feathering: true, - feathering_size_in_pixels: blur * pixels_per_point, - ..Default::default() - }, - font_tex_size, - vec![], - ); - let mut mesh = Mesh::default(); - tessellator.tessellate_rect(&rect, &mut mesh); - mesh + RectShape::filled(rect, rounding, color).with_blur_width(blur) } /// How much larger than the parent rect are we in each direction? diff --git a/crates/epaint/src/shape.rs b/crates/epaint/src/shape.rs index 3c6f1703d17..7922a92d6d5 100644 --- a/crates/epaint/src/shape.rs +++ b/crates/epaint/src/shape.rs @@ -668,6 +668,14 @@ pub struct RectShape { /// The thickness and color of the outline. pub stroke: Stroke, + /// If larger than zero, the edges of the rectangle + /// (for both fill and stroke) will be blurred. + /// + /// This can be used to produce shadows and glow effects. + /// + /// The blur is currently implemented using a simple linear blur in sRGBA gamma space. + pub blur_width: f32, + /// If the rect should be filled with a texture, which one? /// /// The texture is multiplied with [`Self::fill`]. @@ -695,6 +703,7 @@ impl RectShape { rounding: rounding.into(), fill: fill_color.into(), stroke: stroke.into(), + blur_width: 0.0, fill_texture_id: Default::default(), uv: Rect::ZERO, } @@ -711,6 +720,7 @@ impl RectShape { rounding: rounding.into(), fill: fill_color.into(), stroke: Default::default(), + blur_width: 0.0, fill_texture_id: Default::default(), uv: Rect::ZERO, } @@ -723,18 +733,32 @@ impl RectShape { rounding: rounding.into(), fill: Default::default(), stroke: stroke.into(), + blur_width: 0.0, fill_texture_id: Default::default(), uv: Rect::ZERO, } } + /// If larger than zero, the edges of the rectangle + /// (for both fill and stroke) will be blurred. + /// + /// This can be used to produce shadows and glow effects. + /// + /// The blur is currently implemented using a simple linear blur in `sRGBA` gamma space. + #[inline] + pub fn with_blur_width(mut self, blur_width: f32) -> Self { + self.blur_width = blur_width; + self + } + /// The visual bounding rectangle (includes stroke width) #[inline] pub fn visual_bounding_rect(&self) -> Rect { if self.fill == Color32::TRANSPARENT && self.stroke.is_empty() { Rect::NOTHING } else { - self.rect.expand(self.stroke.width / 2.0) + self.rect + .expand((self.stroke.width + self.blur_width) / 2.0) } } } diff --git a/crates/epaint/src/shape_transform.rs b/crates/epaint/src/shape_transform.rs index 7f4d335845f..8ff65d2a045 100644 --- a/crates/epaint/src/shape_transform.rs +++ b/crates/epaint/src/shape_transform.rs @@ -37,6 +37,7 @@ pub fn adjust_colors(shape: &mut Shape, adjust_color: &impl Fn(&mut Color32)) { rounding: _, fill, stroke, + blur_width: _, fill_texture_id: _, uv: _, }) diff --git a/crates/epaint/src/tessellator.rs b/crates/epaint/src/tessellator.rs index f626b9f3094..f5b3d9e3cb1 100644 --- a/crates/epaint/src/tessellator.rs +++ b/crates/epaint/src/tessellator.rs @@ -1503,9 +1503,10 @@ impl Tessellator { pub fn tessellate_rect(&mut self, rect: &RectShape, out: &mut Mesh) { let RectShape { mut rect, - rounding, + mut rounding, fill, stroke, + mut blur_width, fill_texture_id, uv, } = *rect; @@ -1524,6 +1525,29 @@ impl Tessellator { rect.min = rect.min.at_least(pos2(-1e7, -1e7)); rect.max = rect.max.at_most(pos2(1e7, 1e7)); + let old_feathering = self.feathering; + + if old_feathering < blur_width { + // We accomplish the blur by using a larger-than-normal feathering. + // Feathering is usually used to make the edges of a shape softer for anti-aliasing. + + // The tessellator can't handle blurring/feathering larger than the smallest side of the rect. + // Thats because the tessellator approximate very thin rectangles as line segments, + // and these line segments don't have rounded corners. + // When the feathering is small (the size of a pixel), this is usually fine, + // but here we have a huge feathering to simulate blur, + // so we need to avoid this optimization in the tessellator, + // which is also why we add this rather big epsilon: + let eps = 0.1; + blur_width = blur_width + .at_most(rect.size().min_elem() - eps) + .at_least(0.0); + + rounding += Rounding::same(0.5 * blur_width); + + self.feathering = self.feathering.max(blur_width); + } + if rect.width() < self.feathering { // Very thin - approximate by a vertical line-segment: let line = [rect.center_top(), rect.center_bottom()]; @@ -1566,6 +1590,8 @@ impl Tessellator { path.stroke_closed(self.feathering, stroke, out); } + + self.feathering = old_feathering; // restore } /// Tessellate a single [`TextShape`] into a [`Mesh`]. From 810135c5eb0189759a9d58d5e60590271634987a Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 30 Mar 2024 10:15:54 +0100 Subject: [PATCH 061/134] Fix incorrect `Response::interact_rect` for `Area/Window` (#4273) It was wrong for any `Area` or `Window` that was being moved or whose position was constrained --- crates/egui/src/containers/area.rs | 3 ++- crates/egui/src/response.rs | 15 +++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/egui/src/containers/area.rs b/crates/egui/src/containers/area.rs index 5cd0f01dfce..857569da3ac 100644 --- a/crates/egui/src/containers/area.rs +++ b/crates/egui/src/containers/area.rs @@ -355,7 +355,8 @@ impl Area { state.set_left_top_pos(ctx.round_pos_to_pixels(state.left_top_pos())); // Update responsbe with posisbly moved/constrained rect: - move_response = move_response.with_new_rect(state.rect()); + move_response.rect = state.rect(); + move_response.interact_rect = state.rect(); Prepared { layer_id, diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index 32c07ed7b1a..51a53121e1b 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -38,9 +38,6 @@ pub struct Response { /// /// This is sometimes smaller than [`Self::rect`] because of clipping /// (e.g. when inside a scroll area). - /// - /// The interact rect may also be slightly larger than the widget rect, - /// because egui adds half if the item spacing to make the interact rect easier to hit. pub interact_rect: Rect, /// The senses (click and/or drag) that the widget was interested in (if any). @@ -59,7 +56,7 @@ pub struct Response { pub enabled: bool, // OUT: - /// The pointer is hovering above this widget. + /// The pointer is above this widget with no other blocking it. #[doc(hidden)] pub contains_pointer: bool, @@ -200,7 +197,10 @@ impl Response { self.clicked && self.ctx.input(|i| i.pointer.button_triple_clicked(button)) } - /// `true` if there was a click *outside* this widget this frame. + /// `true` if there was a click *outside* the rect of this widget. + /// + /// Clicks on widgets contained in this one counts as clicks inside this widget, + /// so that clicking a button in an area will not be considered as clicking "elsewhere" from the area. pub fn clicked_elsewhere(&self) -> bool { // We do not use self.clicked(), because we want to catch all clicks within our frame, // even if we aren't clickable (or even enabled). @@ -243,14 +243,13 @@ impl Response { self.hovered } - /// Returns true if the pointer is contained by the response rect. + /// Returns true if the pointer is contained by the response rect, and no other widget is covering it. /// /// In contrast to [`Self::hovered`], this can be `true` even if some other widget is being dragged. /// This means it is useful for styling things like drag-and-drop targets. /// `contains_pointer` can also be `true` for disabled widgets. /// - /// This is slightly different from [`Ui::rect_contains_pointer`] and [`Context::rect_contains_pointer`], - /// The rectangle used here is slightly larger, by half of the current item spacing. + /// This is slightly different from [`Ui::rect_contains_pointer`] and [`Context::rect_contains_pointer`], in that /// [`Self::contains_pointer`] also checks that no other widget is covering this response rectangle. #[inline(always)] pub fn contains_pointer(&self) -> bool { From a7c5eb47a8a34195121f6a68de18751da0e1a9a3 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 30 Mar 2024 10:22:58 +0100 Subject: [PATCH 062/134] Fix bug in determining wether to remove focus from a widget (#4272) * Closes https://github.com/emilk/egui/issues/4206 --- crates/egui/src/context.rs | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 222f8ab0575..74a71da2141 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -1113,8 +1113,6 @@ impl Context { changed: false, }; - let clicked_elsewhere = res.clicked_elsewhere(); - self.write(|ctx| { let viewport = ctx.viewports.entry(ctx.viewport_id()).or_default(); @@ -1157,15 +1155,22 @@ impl Context { } let clicked = Some(id) == viewport.interact_widgets.clicked; + let mut any_press = false; for pointer_event in &input.pointer.pointer_events { - if let PointerEvent::Released { click, .. } = pointer_event { - if enabled && sense.click && clicked && click.is_some() { - res.clicked = true; + match pointer_event { + PointerEvent::Moved(_) => {} + PointerEvent::Pressed { .. } => { + any_press = true; } + PointerEvent::Released { click, .. } => { + if enabled && sense.click && clicked && click.is_some() { + res.clicked = true; + } - res.is_pointer_button_down_on = false; - res.dragged = false; + res.is_pointer_button_down_on = false; + res.dragged = false; + } } } @@ -1188,14 +1193,10 @@ impl Context { res.hovered = false; } - if clicked_elsewhere && memory.has_focus(id) { + let pointer_pressed_elsewhere = any_press && !res.hovered; + if pointer_pressed_elsewhere && memory.has_focus(id) { memory.surrender_focus(id); } - - if res.dragged() && !memory.has_focus(id) { - // e.g.: remove focus from a widget when you drag something else - memory.stop_text_input(); - } }); res From 8da0e8cc770cfd89e57009be0c85cbbb19b3975d Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 30 Mar 2024 10:35:55 +0100 Subject: [PATCH 063/134] Fix: `Response::clicked_elsewhere` takes clip rect into account (#4274) `clicked_elsewhere` now checks against the clipped `interact_rect` of the widget instead of the full `rect`. In practice this shouldn't change much since the function is mostly used for windows and areas, which aren't clipped. --- crates/egui/src/response.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index 51a53121e1b..6946e157f5a 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -209,14 +209,10 @@ impl Response { let pointer = &i.pointer; if pointer.any_click() { - // We detect clicks/hover on a "interact_rect" that is slightly larger than - // self.rect. See Context::interact. - // This means we can be hovered and clicked even though `!self.rect.contains(pos)` is true, - // hence the extra complexity here. - if self.contains_pointer() { + if self.contains_pointer || self.hovered { false } else if let Some(pos) = pointer.interact_pos() { - !self.rect.contains(pos) + !self.interact_rect.contains(pos) } else { false // clicked without a pointer, weird } From fbb4a040ac94fc83e64d75bbe6e4c61327d26787 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 30 Mar 2024 10:54:32 +0100 Subject: [PATCH 064/134] Change the resize cursor when you reach the resize limit (#4275) For panels and `DragValue`: if you cannot resize more in one direction, reflect that in the choice of mouse cursor ![resize-cursor-2](https://github.com/emilk/egui/assets/1148717/f95176d3-eab9-48cf-b7bd-3182312551d9) --- crates/egui/src/containers/panel.rs | 40 ++++++++++++++++++--------- crates/egui/src/containers/window.rs | 1 + crates/egui/src/widgets/drag_value.rs | 12 ++++++-- 3 files changed, 38 insertions(+), 15 deletions(-) diff --git a/crates/egui/src/containers/panel.rs b/crates/egui/src/containers/panel.rs index 11e69b8289c..27be59996d4 100644 --- a/crates/egui/src/containers/panel.rs +++ b/crates/egui/src/containers/panel.rs @@ -228,8 +228,8 @@ impl SidePanel { let available_rect = ui.available_rect_before_wrap(); let mut panel_rect = available_rect; + let mut width = default_width; { - let mut width = default_width; if let Some(state) = PanelState::load(ui.ctx(), id) { width = state.rect.width(); } @@ -249,9 +249,8 @@ impl SidePanel { if is_resizing { if let Some(pointer) = resize_response.interact_pointer_pos() { - let width = (pointer.x - side.side_x(panel_rect)).abs(); - let width = - clamp_to_range(width, width_range).at_most(available_rect.width()); + width = (pointer.x - side.side_x(panel_rect)).abs(); + width = clamp_to_range(width, width_range).at_most(available_rect.width()); side.set_rect_width(&mut panel_rect, width); } } @@ -296,7 +295,14 @@ impl SidePanel { } if resize_hover || is_resizing { - ui.ctx().set_cursor_icon(CursorIcon::ResizeHorizontal); + let cursor_icon = if width <= width_range.min { + CursorIcon::ResizeEast + } else if width < width_range.max { + CursorIcon::ResizeHorizontal + } else { + CursorIcon::ResizeWest + }; + ui.ctx().set_cursor_icon(cursor_icon); } PanelState { rect }.store(ui.ctx(), id); @@ -684,12 +690,13 @@ impl TopBottomPanel { let available_rect = ui.available_rect_before_wrap(); let mut panel_rect = available_rect; + + let mut height = if let Some(state) = PanelState::load(ui.ctx(), id) { + state.rect.height() + } else { + default_height.unwrap_or_else(|| ui.style().spacing.interact_size.y) + }; { - let mut height = if let Some(state) = PanelState::load(ui.ctx(), id) { - state.rect.height() - } else { - default_height.unwrap_or_else(|| ui.style().spacing.interact_size.y) - }; height = clamp_to_range(height, height_range).at_most(available_rect.height()); side.set_rect_height(&mut panel_rect, height); ui.ctx() @@ -707,8 +714,8 @@ impl TopBottomPanel { if is_resizing { if let Some(pointer) = resize_response.interact_pointer_pos() { - let height = (pointer.y - side.side_y(panel_rect)).abs(); - let height = + height = (pointer.y - side.side_y(panel_rect)).abs(); + height = clamp_to_range(height, height_range).at_most(available_rect.height()); side.set_rect_height(&mut panel_rect, height); } @@ -755,7 +762,14 @@ impl TopBottomPanel { } if resize_hover || is_resizing { - ui.ctx().set_cursor_icon(CursorIcon::ResizeVertical); + let cursor_icon = if height <= height_range.min { + CursorIcon::ResizeSouth + } else if height < height_range.max { + CursorIcon::ResizeVertical + } else { + CursorIcon::ResizeNorth + }; + ui.ctx().set_cursor_icon(cursor_icon); } PanelState { rect }.store(ui.ctx(), id); diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index af6f6b16c31..619675ca8a5 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -697,6 +697,7 @@ impl ResizeInteraction { let top = self.top.any(); let bottom = self.bottom.any(); + // TODO(emilk): use one-sided cursors for when we reached the min/max size. if (left && top) || (right && bottom) { ctx.set_cursor_icon(CursorIcon::ResizeNwSe); } else if (right && top) || (left && bottom) { diff --git a/crates/egui/src/widgets/drag_value.rs b/crates/egui/src/widgets/drag_value.rs index f0875ba3055..8a7f19028b6 100644 --- a/crates/egui/src/widgets/drag_value.rs +++ b/crates/egui/src/widgets/drag_value.rs @@ -525,8 +525,16 @@ impl<'a> Widget for DragValue<'a> { .sense(Sense::click_and_drag()) .min_size(ui.spacing().interact_size); // TODO(emilk): find some more generic solution to `min_size` + let cursor_icon = if value <= *clamp_range.start() { + CursorIcon::ResizeEast + } else if value < *clamp_range.end() { + CursorIcon::ResizeHorizontal + } else { + CursorIcon::ResizeWest + }; + let response = ui.add(button); - let mut response = response.on_hover_cursor(CursorIcon::ResizeHorizontal); + let mut response = response.on_hover_cursor(cursor_icon); if ui.style().explanation_tooltips { response = response.on_hover_text(format!( @@ -552,7 +560,7 @@ impl<'a> Widget for DragValue<'a> { ))); state.store(ui.ctx(), response.id); } else if response.dragged() { - ui.ctx().set_cursor_icon(CursorIcon::ResizeHorizontal); + ui.ctx().set_cursor_icon(cursor_icon); let mdelta = response.drag_delta(); let delta_points = mdelta.x - mdelta.y; // Increase to the right and up From 32888e0f83aa3ecf287f8052db43d8ed38f3f5b6 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 30 Mar 2024 13:33:51 +0100 Subject: [PATCH 065/134] Make `TextEdit` and atomic widget (#4276) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This means only one space allocation, which should allow putting it in more types of layouts (right-to-left, centered, adjusted, …). It also just makes the code simpler --- crates/egui/src/widgets/text_edit/builder.rs | 56 ++++++++------------ 1 file changed, 23 insertions(+), 33 deletions(-) diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index 799774096ff..a8f60d1a5df 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -389,29 +389,20 @@ impl<'t> TextEdit<'t> { pub fn show(self, ui: &mut Ui) -> TextEditOutput { let is_mutable = self.text.is_mutable(); let frame = self.frame; - let interactive = self.interactive; let where_to_put_background = ui.painter().add(Shape::Noop); let margin = self.margin; - let available = ui.available_rect_before_wrap(); - let max_rect = margin.shrink_rect(available); - let mut content_ui = ui.child_ui(max_rect, *ui.layout()); + let mut output = self.show_content(ui); - let mut output = self.show_content(&mut content_ui); - - let id = output.response.id; - let frame_rect = margin.expand_rect(output.response.rect); - ui.allocate_space(frame_rect.size()); - if interactive { - output.response |= ui.interact(frame_rect, id, Sense::click()); - } - if output.response.clicked() && !output.response.lost_focus() { - ui.memory_mut(|mem| mem.request_focus(output.response.id)); - } + // TODO(emilk): return full outer_rect in `TextEditOutput`. + // Can't do it now because this fix is ging into a patch release. + let outer_rect = output.response.rect; + let inner_rect = margin.shrink_rect(outer_rect); + output.response.rect = inner_rect; if frame { let visuals = ui.style().interact(&output.response); - let frame_rect = frame_rect.expand(visuals.expansion); + let frame_rect = outer_rect.expand(visuals.expansion); let shape = if is_mutable { if output.response.has_focus() { epaint::RectShape::new( @@ -478,7 +469,7 @@ impl<'t> TextEdit<'t> { let font_id = font_selection.resolve(ui.style()); let row_height = ui.fonts(|f| f.row_height(&font_id)); const MIN_WIDTH: f32 = 24.0; // Never make a [`TextEdit`] more narrow than this. - let available_width = ui.available_width().at_least(MIN_WIDTH); + let available_width = (ui.available_width() - margin.sum().x).at_least(MIN_WIDTH); let desired_width = desired_width.unwrap_or_else(|| ui.spacing().text_edit_width); let wrap_width = if ui.layout().horizontal_justify() { available_width @@ -507,11 +498,10 @@ impl<'t> TextEdit<'t> { galley.size().x.max(wrap_width) }; let desired_height = (desired_height_rows.at_least(1) as f32) * row_height; - let at_least = min_size - margin.sum(); - let desired_size = - vec2(desired_width, galley.size().y.max(desired_height)).at_least(at_least); - - let (auto_id, rect) = ui.allocate_space(desired_size); + let desired_inner_size = vec2(desired_width, galley.size().y.max(desired_height)); + let desired_outer_size = (desired_inner_size + margin.sum()).at_least(min_size); + let (auto_id, outer_rect) = ui.allocate_space(desired_outer_size); + let rect = margin.shrink_rect(outer_rect); // inner rect (excluding frame/margin). let id = id.unwrap_or_else(|| { if let Some(id_source) = id_source { @@ -538,7 +528,7 @@ impl<'t> TextEdit<'t> { } else { Sense::hover() }; - let mut response = ui.interact(rect, id, sense); + let mut response = ui.interact(outer_rect, id, sense); let text_clip_rect = rect; let painter = ui.painter_at(text_clip_rect.expand(1.0)); // expand to avoid clipping cursor @@ -552,7 +542,7 @@ impl<'t> TextEdit<'t> { let singleline_offset = vec2(state.singleline_offset, 0.0); let cursor_at_pointer = - galley.cursor_from_pos(pointer_pos - response.rect.min + singleline_offset); + galley.cursor_from_pos(pointer_pos - rect.min + singleline_offset); if ui.visuals().text_cursor_preview && response.hovered() @@ -560,7 +550,7 @@ impl<'t> TextEdit<'t> { { // preview: let cursor_rect = - cursor_rect(response.rect.min, &galley, &cursor_at_pointer, row_height); + cursor_rect(rect.min, &galley, &cursor_at_pointer, row_height); paint_cursor(&painter, ui.visuals(), cursor_rect); } @@ -617,10 +607,10 @@ impl<'t> TextEdit<'t> { } let mut galley_pos = align - .align_size_within_rect(galley.size(), response.rect) - .intersect(response.rect) // limit pos to the response rect area + .align_size_within_rect(galley.size(), rect) + .intersect(rect) // limit pos to the response rect area .min; - let align_offset = response.rect.left() - galley_pos.x; + let align_offset = rect.left() - galley_pos.x; // Visual clipping for singleline text editor with text larger than width if clip_text && align_offset == 0.0 { @@ -630,18 +620,18 @@ impl<'t> TextEdit<'t> { }; let mut offset_x = state.singleline_offset; - let visible_range = offset_x..=offset_x + desired_size.x; + let visible_range = offset_x..=offset_x + desired_inner_size.x; if !visible_range.contains(&cursor_pos) { if cursor_pos < *visible_range.start() { offset_x = cursor_pos; } else { - offset_x = cursor_pos - desired_size.x; + offset_x = cursor_pos - desired_inner_size.x; } } offset_x = offset_x - .at_most(galley.size().x - desired_size.x) + .at_most(galley.size().x - desired_inner_size.x) .at_least(0.0); state.singleline_offset = offset_x; @@ -664,11 +654,11 @@ impl<'t> TextEdit<'t> { if text.as_str().is_empty() && !hint_text.is_empty() { let hint_text_color = ui.visuals().weak_text_color(); let galley = if multiline { - hint_text.into_galley(ui, Some(true), desired_size.x, font_id) + hint_text.into_galley(ui, Some(true), desired_inner_size.x, font_id) } else { hint_text.into_galley(ui, Some(false), f32::INFINITY, font_id) }; - painter.galley(response.rect.min, galley, hint_text_color); + painter.galley(rect.min, galley, hint_text_color); } if ui.memory(|mem| mem.has_focus(id)) { From a9a756e8f3e827c5f2d53d35029d580ef05e286c Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 30 Mar 2024 14:03:41 +0100 Subject: [PATCH 066/134] Overload operators for `Rect + Margin`, `Rect - Margin` etc (#4277) It's more ergonomic --- crates/egui/src/containers/frame.rs | 9 +- crates/egui/src/widgets/text_edit/builder.rs | 4 +- crates/epaint/src/margin.rs | 108 ++++++++++++++----- 3 files changed, 89 insertions(+), 32 deletions(-) diff --git a/crates/egui/src/containers/frame.rs b/crates/egui/src/containers/frame.rs index 6d7ba6e6274..b982cfd0b03 100644 --- a/crates/egui/src/containers/frame.rs +++ b/crates/egui/src/containers/frame.rs @@ -240,7 +240,7 @@ impl Frame { let where_to_put_background = ui.painter().add(Shape::Noop); let outer_rect_bounds = ui.available_rect_before_wrap(); - let mut inner_rect = (self.inner_margin + self.outer_margin).shrink_rect(outer_rect_bounds); + let mut inner_rect = outer_rect_bounds - self.outer_margin - self.inner_margin; // Make sure we don't shrink to the negative: inner_rect.max.x = inner_rect.max.x.max(inner_rect.min.x); @@ -299,7 +299,7 @@ impl Frame { impl Prepared { fn content_with_margin(&self) -> Rect { - (self.frame.inner_margin + self.frame.outer_margin).expand_rect(self.content_ui.min_rect()) + self.content_ui.min_rect() + self.frame.inner_margin + self.frame.outer_margin } /// Allocate the the space that was used by [`Self::content_ui`]. @@ -315,10 +315,7 @@ impl Prepared { /// /// This can be called before or after [`Self::allocate_space`]. pub fn paint(&self, ui: &Ui) { - let paint_rect = self - .frame - .inner_margin - .expand_rect(self.content_ui.min_rect()); + let paint_rect = self.content_ui.min_rect() + self.frame.inner_margin; if ui.is_rect_visible(paint_rect) { let shape = self.frame.paint(paint_rect); diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index a8f60d1a5df..e320a1ae843 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -397,7 +397,7 @@ impl<'t> TextEdit<'t> { // TODO(emilk): return full outer_rect in `TextEditOutput`. // Can't do it now because this fix is ging into a patch release. let outer_rect = output.response.rect; - let inner_rect = margin.shrink_rect(outer_rect); + let inner_rect = outer_rect - margin; output.response.rect = inner_rect; if frame { @@ -501,7 +501,7 @@ impl<'t> TextEdit<'t> { let desired_inner_size = vec2(desired_width, galley.size().y.max(desired_height)); let desired_outer_size = (desired_inner_size + margin.sum()).at_least(min_size); let (auto_id, outer_rect) = ui.allocate_space(desired_outer_size); - let rect = margin.shrink_rect(outer_rect); // inner rect (excluding frame/margin). + let rect = outer_rect - margin; // inner rect (excluding frame/margin). let id = id.unwrap_or_else(|| { if let Some(id_source) = id_source { diff --git a/crates/epaint/src/margin.rs b/crates/epaint/src/margin.rs index 815ff835163..e2ade58f9c2 100644 --- a/crates/epaint/src/margin.rs +++ b/crates/epaint/src/margin.rs @@ -2,6 +2,8 @@ use emath::{vec2, Rect, Vec2}; /// A value for all four sides of a rectangle, /// often used to express padding or spacing. +/// +/// Can be added and subtracted to/from [`Rect`]s. #[derive(Clone, Copy, Debug, Default, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct Margin { @@ -19,6 +21,8 @@ impl Margin { bottom: 0.0, }; + /// The same margin on every side. + #[doc(alias = "symmetric")] #[inline] pub const fn same(margin: f32) -> Self { Self { @@ -56,16 +60,20 @@ impl Margin { vec2(self.right, self.bottom) } + /// Are the margin on every side the same? + #[doc(alias = "symmetric")] #[inline] pub fn is_same(&self) -> bool { self.left == self.right && self.left == self.top && self.left == self.bottom } + #[deprecated = "Use `rect + margin` instead"] #[inline] pub fn expand_rect(&self, rect: Rect) -> Rect { Rect::from_min_max(rect.min - self.left_top(), rect.max + self.right_bottom()) } + #[deprecated = "Use `rect - margin` instead"] #[inline] pub fn shrink_rect(&self, rect: Rect) -> Rect { Rect::from_min_max(rect.min + self.left_top(), rect.max - self.right_bottom()) @@ -86,6 +94,7 @@ impl From for Margin { } } +/// `Margin + Margin` impl std::ops::Add for Margin { type Output = Self; @@ -100,6 +109,7 @@ impl std::ops::Add for Margin { } } +/// `Margin + f32` impl std::ops::Add for Margin { type Output = Self; @@ -114,6 +124,7 @@ impl std::ops::Add for Margin { } } +/// `Margind += f32` impl std::ops::AddAssign for Margin { #[inline] fn add_assign(&mut self, v: f32) { @@ -124,30 +135,7 @@ impl std::ops::AddAssign for Margin { } } -impl std::ops::Div for Margin { - type Output = Self; - - #[inline] - fn div(self, v: f32) -> Self { - Self { - left: self.left / v, - right: self.right / v, - top: self.top / v, - bottom: self.bottom / v, - } - } -} - -impl std::ops::DivAssign for Margin { - #[inline] - fn div_assign(&mut self, v: f32) { - self.left /= v; - self.right /= v; - self.top /= v; - self.bottom /= v; - } -} - +/// `Margin * f32` impl std::ops::Mul for Margin { type Output = Self; @@ -162,6 +150,7 @@ impl std::ops::Mul for Margin { } } +/// `Margin *= f32` impl std::ops::MulAssign for Margin { #[inline] fn mul_assign(&mut self, v: f32) { @@ -172,6 +161,33 @@ impl std::ops::MulAssign for Margin { } } +/// `Margin / f32` +impl std::ops::Div for Margin { + type Output = Self; + + #[inline] + fn div(self, v: f32) -> Self { + Self { + left: self.left / v, + right: self.right / v, + top: self.top / v, + bottom: self.bottom / v, + } + } +} + +/// `Margin /= f32` +impl std::ops::DivAssign for Margin { + #[inline] + fn div_assign(&mut self, v: f32) { + self.left /= v; + self.right /= v; + self.top /= v; + self.bottom /= v; + } +} + +/// `Margin - Margin` impl std::ops::Sub for Margin { type Output = Self; @@ -186,6 +202,7 @@ impl std::ops::Sub for Margin { } } +/// `Margin - f32` impl std::ops::Sub for Margin { type Output = Self; @@ -200,6 +217,7 @@ impl std::ops::Sub for Margin { } } +/// `Margin -= f32` impl std::ops::SubAssign for Margin { #[inline] fn sub_assign(&mut self, v: f32) { @@ -209,3 +227,45 @@ impl std::ops::SubAssign for Margin { self.bottom -= v; } } + +/// `Rect + Margin` +impl std::ops::Add for Rect { + type Output = Self; + + #[inline] + fn add(self, margin: Margin) -> Self { + Self::from_min_max( + self.min - margin.left_top(), + self.max + margin.right_bottom(), + ) + } +} + +/// `Rect += Margin` +impl std::ops::AddAssign for Rect { + #[inline] + fn add_assign(&mut self, margin: Margin) { + *self = *self + margin; + } +} + +/// `Rect - Margin` +impl std::ops::Sub for Rect { + type Output = Self; + + #[inline] + fn sub(self, margin: Margin) -> Self { + Self::from_min_max( + self.min + margin.left_top(), + self.max - margin.right_bottom(), + ) + } +} + +/// `Rect -= Margin` +impl std::ops::SubAssign for Rect { + #[inline] + fn sub_assign(&mut self, margin: Margin) { + *self = *self - margin; + } +} From 727732298326988c033d2a1be7ae93161a84a4cc Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 30 Mar 2024 14:28:12 +0100 Subject: [PATCH 067/134] Break out Checkbox, RadioButton and ImageButton to their own files (#4278) Just a refactor --- crates/egui/src/widgets/button.rs | 376 ------------------------ crates/egui/src/widgets/checkbox.rs | 136 +++++++++ crates/egui/src/widgets/image_button.rs | 130 ++++++++ crates/egui/src/widgets/mod.rs | 30 +- crates/egui/src/widgets/radio_button.rs | 107 +++++++ 5 files changed, 392 insertions(+), 387 deletions(-) create mode 100644 crates/egui/src/widgets/checkbox.rs create mode 100644 crates/egui/src/widgets/image_button.rs create mode 100644 crates/egui/src/widgets/radio_button.rs diff --git a/crates/egui/src/widgets/button.rs b/crates/egui/src/widgets/button.rs index 75da2a9c716..76a62125045 100644 --- a/crates/egui/src/widgets/button.rs +++ b/crates/egui/src/widgets/button.rs @@ -336,379 +336,3 @@ impl Widget for Button<'_> { response } } - -// ---------------------------------------------------------------------------- - -// TODO(emilk): allow checkbox without a text label -/// Boolean on/off control with text label. -/// -/// Usually you'd use [`Ui::checkbox`] instead. -/// -/// ``` -/// # egui::__run_test_ui(|ui| { -/// # let mut my_bool = true; -/// // These are equivalent: -/// ui.checkbox(&mut my_bool, "Checked"); -/// ui.add(egui::Checkbox::new(&mut my_bool, "Checked")); -/// # }); -/// ``` -#[must_use = "You should put this widget in an ui with `ui.add(widget);`"] -pub struct Checkbox<'a> { - checked: &'a mut bool, - text: WidgetText, - indeterminate: bool, -} - -impl<'a> Checkbox<'a> { - pub fn new(checked: &'a mut bool, text: impl Into) -> Self { - Checkbox { - checked, - text: text.into(), - indeterminate: false, - } - } - - pub fn without_text(checked: &'a mut bool) -> Self { - Self::new(checked, WidgetText::default()) - } - - /// Display an indeterminate state (neither checked nor unchecked) - /// - /// This only affects the checkbox's appearance. It will still toggle its boolean value when - /// clicked. - #[inline] - pub fn indeterminate(mut self, indeterminate: bool) -> Self { - self.indeterminate = indeterminate; - self - } -} - -impl<'a> Widget for Checkbox<'a> { - fn ui(self, ui: &mut Ui) -> Response { - let Checkbox { - checked, - text, - indeterminate, - } = self; - - let spacing = &ui.spacing(); - let icon_width = spacing.icon_width; - let icon_spacing = spacing.icon_spacing; - - let (galley, mut desired_size) = if text.is_empty() { - (None, vec2(icon_width, 0.0)) - } else { - let total_extra = vec2(icon_width + icon_spacing, 0.0); - - let wrap_width = ui.available_width() - total_extra.x; - let galley = text.into_galley(ui, None, wrap_width, TextStyle::Button); - - let mut desired_size = total_extra + galley.size(); - desired_size = desired_size.at_least(spacing.interact_size); - - (Some(galley), desired_size) - }; - - desired_size = desired_size.at_least(Vec2::splat(spacing.interact_size.y)); - desired_size.y = desired_size.y.max(icon_width); - let (rect, mut response) = ui.allocate_exact_size(desired_size, Sense::click()); - - if response.clicked() { - *checked = !*checked; - response.mark_changed(); - } - response.widget_info(|| { - if indeterminate { - WidgetInfo::labeled( - WidgetType::Checkbox, - galley.as_ref().map_or("", |x| x.text()), - ) - } else { - WidgetInfo::selected( - WidgetType::Checkbox, - *checked, - galley.as_ref().map_or("", |x| x.text()), - ) - } - }); - - if ui.is_rect_visible(rect) { - // let visuals = ui.style().interact_selectable(&response, *checked); // too colorful - let visuals = ui.style().interact(&response); - let (small_icon_rect, big_icon_rect) = ui.spacing().icon_rectangles(rect); - ui.painter().add(epaint::RectShape::new( - big_icon_rect.expand(visuals.expansion), - visuals.rounding, - visuals.bg_fill, - visuals.bg_stroke, - )); - - if indeterminate { - // Horizontal line: - ui.painter().add(Shape::hline( - small_icon_rect.x_range(), - small_icon_rect.center().y, - visuals.fg_stroke, - )); - } else if *checked { - // Check mark: - ui.painter().add(Shape::line( - vec![ - pos2(small_icon_rect.left(), small_icon_rect.center().y), - pos2(small_icon_rect.center().x, small_icon_rect.bottom()), - pos2(small_icon_rect.right(), small_icon_rect.top()), - ], - visuals.fg_stroke, - )); - } - if let Some(galley) = galley { - let text_pos = pos2( - rect.min.x + icon_width + icon_spacing, - rect.center().y - 0.5 * galley.size().y, - ); - ui.painter().galley(text_pos, galley, visuals.text_color()); - } - } - - response - } -} - -// ---------------------------------------------------------------------------- - -/// One out of several alternatives, either selected or not. -/// -/// Usually you'd use [`Ui::radio_value`] or [`Ui::radio`] instead. -/// -/// ``` -/// # egui::__run_test_ui(|ui| { -/// #[derive(PartialEq)] -/// enum Enum { First, Second, Third } -/// let mut my_enum = Enum::First; -/// -/// ui.radio_value(&mut my_enum, Enum::First, "First"); -/// -/// // is equivalent to: -/// -/// if ui.add(egui::RadioButton::new(my_enum == Enum::First, "First")).clicked() { -/// my_enum = Enum::First -/// } -/// # }); -/// ``` -#[must_use = "You should put this widget in an ui with `ui.add(widget);`"] -pub struct RadioButton { - checked: bool, - text: WidgetText, -} - -impl RadioButton { - pub fn new(checked: bool, text: impl Into) -> Self { - Self { - checked, - text: text.into(), - } - } -} - -impl Widget for RadioButton { - fn ui(self, ui: &mut Ui) -> Response { - let Self { checked, text } = self; - - let spacing = &ui.spacing(); - let icon_width = spacing.icon_width; - let icon_spacing = spacing.icon_spacing; - - let (galley, mut desired_size) = if text.is_empty() { - (None, vec2(icon_width, 0.0)) - } else { - let total_extra = vec2(icon_width + icon_spacing, 0.0); - - let wrap_width = ui.available_width() - total_extra.x; - let text = text.into_galley(ui, None, wrap_width, TextStyle::Button); - - let mut desired_size = total_extra + text.size(); - desired_size = desired_size.at_least(spacing.interact_size); - - (Some(text), desired_size) - }; - - desired_size = desired_size.at_least(Vec2::splat(spacing.interact_size.y)); - desired_size.y = desired_size.y.max(icon_width); - let (rect, response) = ui.allocate_exact_size(desired_size, Sense::click()); - - response.widget_info(|| { - WidgetInfo::selected( - WidgetType::RadioButton, - checked, - galley.as_ref().map_or("", |x| x.text()), - ) - }); - - if ui.is_rect_visible(rect) { - // let visuals = ui.style().interact_selectable(&response, checked); // too colorful - let visuals = ui.style().interact(&response); - - let (small_icon_rect, big_icon_rect) = ui.spacing().icon_rectangles(rect); - - let painter = ui.painter(); - - painter.add(epaint::CircleShape { - center: big_icon_rect.center(), - radius: big_icon_rect.width() / 2.0 + visuals.expansion, - fill: visuals.bg_fill, - stroke: visuals.bg_stroke, - }); - - if checked { - painter.add(epaint::CircleShape { - center: small_icon_rect.center(), - radius: small_icon_rect.width() / 3.0, - fill: visuals.fg_stroke.color, // Intentional to use stroke and not fill - // fill: ui.visuals().selection.stroke.color, // too much color - stroke: Default::default(), - }); - } - - if let Some(galley) = galley { - let text_pos = pos2( - rect.min.x + icon_width + icon_spacing, - rect.center().y - 0.5 * galley.size().y, - ); - ui.painter().galley(text_pos, galley, visuals.text_color()); - } - } - - response - } -} - -// ---------------------------------------------------------------------------- - -/// A clickable image within a frame. -#[must_use = "You should put this widget in an ui with `ui.add(widget);`"] -#[derive(Clone, Debug)] -pub struct ImageButton<'a> { - image: Image<'a>, - sense: Sense, - frame: bool, - selected: bool, -} - -impl<'a> ImageButton<'a> { - pub fn new(image: impl Into>) -> Self { - Self { - image: image.into(), - sense: Sense::click(), - frame: true, - selected: false, - } - } - - /// Select UV range. Default is (0,0) in top-left, (1,1) bottom right. - #[inline] - pub fn uv(mut self, uv: impl Into) -> Self { - self.image = self.image.uv(uv); - self - } - - /// Multiply image color with this. Default is WHITE (no tint). - #[inline] - pub fn tint(mut self, tint: impl Into) -> Self { - self.image = self.image.tint(tint); - self - } - - /// If `true`, mark this button as "selected". - #[inline] - pub fn selected(mut self, selected: bool) -> Self { - self.selected = selected; - self - } - - /// Turn off the frame - #[inline] - pub fn frame(mut self, frame: bool) -> Self { - self.frame = frame; - self - } - - /// By default, buttons senses clicks. - /// Change this to a drag-button with `Sense::drag()`. - #[inline] - pub fn sense(mut self, sense: Sense) -> Self { - self.sense = sense; - self - } - - /// Set rounding for the `ImageButton`. - /// If the underlying image already has rounding, this - /// will override that value. - #[inline] - pub fn rounding(mut self, rounding: impl Into) -> Self { - self.image = self.image.rounding(rounding.into()); - self - } -} - -impl<'a> Widget for ImageButton<'a> { - fn ui(self, ui: &mut Ui) -> Response { - let padding = if self.frame { - // so we can see that it is a button: - Vec2::splat(ui.spacing().button_padding.x) - } else { - Vec2::ZERO - }; - - let available_size_for_image = ui.available_size() - 2.0 * padding; - let tlr = self.image.load_for_size(ui.ctx(), available_size_for_image); - let original_image_size = tlr.as_ref().ok().and_then(|t| t.size()); - let image_size = self - .image - .calc_size(available_size_for_image, original_image_size); - - let padded_size = image_size + 2.0 * padding; - let (rect, response) = ui.allocate_exact_size(padded_size, self.sense); - response.widget_info(|| WidgetInfo::new(WidgetType::ImageButton)); - - if ui.is_rect_visible(rect) { - let (expansion, rounding, fill, stroke) = if self.selected { - let selection = ui.visuals().selection; - ( - Vec2::ZERO, - self.image.image_options().rounding, - selection.bg_fill, - selection.stroke, - ) - } else if self.frame { - let visuals = ui.style().interact(&response); - let expansion = Vec2::splat(visuals.expansion); - ( - expansion, - self.image.image_options().rounding, - visuals.weak_bg_fill, - visuals.bg_stroke, - ) - } else { - Default::default() - }; - - // Draw frame background (for transparent images): - ui.painter() - .rect_filled(rect.expand2(expansion), rounding, fill); - - let image_rect = ui - .layout() - .align_size_within_rect(image_size, rect.shrink2(padding)); - // let image_rect = image_rect.expand2(expansion); // can make it blurry, so let's not - let image_options = self.image.image_options().clone(); - - widgets::image::paint_texture_load_result(ui, &tlr, image_rect, None, &image_options); - - // Draw frame outline: - ui.painter() - .rect_stroke(rect.expand2(expansion), rounding, stroke); - } - - widgets::image::texture_load_result_response(self.image.source(), &tlr, response) - } -} diff --git a/crates/egui/src/widgets/checkbox.rs b/crates/egui/src/widgets/checkbox.rs new file mode 100644 index 00000000000..e416a6e0681 --- /dev/null +++ b/crates/egui/src/widgets/checkbox.rs @@ -0,0 +1,136 @@ +use crate::*; + +// TODO(emilk): allow checkbox without a text label +/// Boolean on/off control with text label. +/// +/// Usually you'd use [`Ui::checkbox`] instead. +/// +/// ``` +/// # egui::__run_test_ui(|ui| { +/// # let mut my_bool = true; +/// // These are equivalent: +/// ui.checkbox(&mut my_bool, "Checked"); +/// ui.add(egui::Checkbox::new(&mut my_bool, "Checked")); +/// # }); +/// ``` +#[must_use = "You should put this widget in an ui with `ui.add(widget);`"] +pub struct Checkbox<'a> { + checked: &'a mut bool, + text: WidgetText, + indeterminate: bool, +} + +impl<'a> Checkbox<'a> { + pub fn new(checked: &'a mut bool, text: impl Into) -> Self { + Checkbox { + checked, + text: text.into(), + indeterminate: false, + } + } + + pub fn without_text(checked: &'a mut bool) -> Self { + Self::new(checked, WidgetText::default()) + } + + /// Display an indeterminate state (neither checked nor unchecked) + /// + /// This only affects the checkbox's appearance. It will still toggle its boolean value when + /// clicked. + #[inline] + pub fn indeterminate(mut self, indeterminate: bool) -> Self { + self.indeterminate = indeterminate; + self + } +} + +impl<'a> Widget for Checkbox<'a> { + fn ui(self, ui: &mut Ui) -> Response { + let Checkbox { + checked, + text, + indeterminate, + } = self; + + let spacing = &ui.spacing(); + let icon_width = spacing.icon_width; + let icon_spacing = spacing.icon_spacing; + + let (galley, mut desired_size) = if text.is_empty() { + (None, vec2(icon_width, 0.0)) + } else { + let total_extra = vec2(icon_width + icon_spacing, 0.0); + + let wrap_width = ui.available_width() - total_extra.x; + let galley = text.into_galley(ui, None, wrap_width, TextStyle::Button); + + let mut desired_size = total_extra + galley.size(); + desired_size = desired_size.at_least(spacing.interact_size); + + (Some(galley), desired_size) + }; + + desired_size = desired_size.at_least(Vec2::splat(spacing.interact_size.y)); + desired_size.y = desired_size.y.max(icon_width); + let (rect, mut response) = ui.allocate_exact_size(desired_size, Sense::click()); + + if response.clicked() { + *checked = !*checked; + response.mark_changed(); + } + response.widget_info(|| { + if indeterminate { + WidgetInfo::labeled( + WidgetType::Checkbox, + galley.as_ref().map_or("", |x| x.text()), + ) + } else { + WidgetInfo::selected( + WidgetType::Checkbox, + *checked, + galley.as_ref().map_or("", |x| x.text()), + ) + } + }); + + if ui.is_rect_visible(rect) { + // let visuals = ui.style().interact_selectable(&response, *checked); // too colorful + let visuals = ui.style().interact(&response); + let (small_icon_rect, big_icon_rect) = ui.spacing().icon_rectangles(rect); + ui.painter().add(epaint::RectShape::new( + big_icon_rect.expand(visuals.expansion), + visuals.rounding, + visuals.bg_fill, + visuals.bg_stroke, + )); + + if indeterminate { + // Horizontal line: + ui.painter().add(Shape::hline( + small_icon_rect.x_range(), + small_icon_rect.center().y, + visuals.fg_stroke, + )); + } else if *checked { + // Check mark: + ui.painter().add(Shape::line( + vec![ + pos2(small_icon_rect.left(), small_icon_rect.center().y), + pos2(small_icon_rect.center().x, small_icon_rect.bottom()), + pos2(small_icon_rect.right(), small_icon_rect.top()), + ], + visuals.fg_stroke, + )); + } + if let Some(galley) = galley { + let text_pos = pos2( + rect.min.x + icon_width + icon_spacing, + rect.center().y - 0.5 * galley.size().y, + ); + ui.painter().galley(text_pos, galley, visuals.text_color()); + } + } + + response + } +} diff --git a/crates/egui/src/widgets/image_button.rs b/crates/egui/src/widgets/image_button.rs new file mode 100644 index 00000000000..65ef3072807 --- /dev/null +++ b/crates/egui/src/widgets/image_button.rs @@ -0,0 +1,130 @@ +use crate::*; + +/// A clickable image within a frame. +#[must_use = "You should put this widget in an ui with `ui.add(widget);`"] +#[derive(Clone, Debug)] +pub struct ImageButton<'a> { + image: Image<'a>, + sense: Sense, + frame: bool, + selected: bool, +} + +impl<'a> ImageButton<'a> { + pub fn new(image: impl Into>) -> Self { + Self { + image: image.into(), + sense: Sense::click(), + frame: true, + selected: false, + } + } + + /// Select UV range. Default is (0,0) in top-left, (1,1) bottom right. + #[inline] + pub fn uv(mut self, uv: impl Into) -> Self { + self.image = self.image.uv(uv); + self + } + + /// Multiply image color with this. Default is WHITE (no tint). + #[inline] + pub fn tint(mut self, tint: impl Into) -> Self { + self.image = self.image.tint(tint); + self + } + + /// If `true`, mark this button as "selected". + #[inline] + pub fn selected(mut self, selected: bool) -> Self { + self.selected = selected; + self + } + + /// Turn off the frame + #[inline] + pub fn frame(mut self, frame: bool) -> Self { + self.frame = frame; + self + } + + /// By default, buttons senses clicks. + /// Change this to a drag-button with `Sense::drag()`. + #[inline] + pub fn sense(mut self, sense: Sense) -> Self { + self.sense = sense; + self + } + + /// Set rounding for the `ImageButton`. + /// If the underlying image already has rounding, this + /// will override that value. + #[inline] + pub fn rounding(mut self, rounding: impl Into) -> Self { + self.image = self.image.rounding(rounding.into()); + self + } +} + +impl<'a> Widget for ImageButton<'a> { + fn ui(self, ui: &mut Ui) -> Response { + let padding = if self.frame { + // so we can see that it is a button: + Vec2::splat(ui.spacing().button_padding.x) + } else { + Vec2::ZERO + }; + + let available_size_for_image = ui.available_size() - 2.0 * padding; + let tlr = self.image.load_for_size(ui.ctx(), available_size_for_image); + let original_image_size = tlr.as_ref().ok().and_then(|t| t.size()); + let image_size = self + .image + .calc_size(available_size_for_image, original_image_size); + + let padded_size = image_size + 2.0 * padding; + let (rect, response) = ui.allocate_exact_size(padded_size, self.sense); + response.widget_info(|| WidgetInfo::new(WidgetType::ImageButton)); + + if ui.is_rect_visible(rect) { + let (expansion, rounding, fill, stroke) = if self.selected { + let selection = ui.visuals().selection; + ( + Vec2::ZERO, + self.image.image_options().rounding, + selection.bg_fill, + selection.stroke, + ) + } else if self.frame { + let visuals = ui.style().interact(&response); + let expansion = Vec2::splat(visuals.expansion); + ( + expansion, + self.image.image_options().rounding, + visuals.weak_bg_fill, + visuals.bg_stroke, + ) + } else { + Default::default() + }; + + // Draw frame background (for transparent images): + ui.painter() + .rect_filled(rect.expand2(expansion), rounding, fill); + + let image_rect = ui + .layout() + .align_size_within_rect(image_size, rect.shrink2(padding)); + // let image_rect = image_rect.expand2(expansion); // can make it blurry, so let's not + let image_options = self.image.image_options().clone(); + + widgets::image::paint_texture_load_result(ui, &tlr, image_rect, None, &image_options); + + // Draw frame outline: + ui.painter() + .rect_stroke(rect.expand2(expansion), rounding, stroke); + } + + widgets::image::texture_load_result_response(self.image.source(), &tlr, response) + } +} diff --git a/crates/egui/src/widgets/mod.rs b/crates/egui/src/widgets/mod.rs index 6b3fb72e0c5..0ab9273c7a5 100644 --- a/crates/egui/src/widgets/mod.rs +++ b/crates/egui/src/widgets/mod.rs @@ -7,29 +7,37 @@ use crate::*; mod button; +mod checkbox; pub mod color_picker; pub(crate) mod drag_value; mod hyperlink; mod image; +mod image_button; mod label; mod progress_bar; +mod radio_button; mod selected_label; mod separator; mod slider; mod spinner; pub mod text_edit; -pub use button::*; -pub use drag_value::DragValue; -pub use hyperlink::*; -pub use image::{paint_texture_at, Image, ImageFit, ImageOptions, ImageSize, ImageSource}; -pub use label::*; -pub use progress_bar::ProgressBar; -pub use selected_label::SelectableLabel; -pub use separator::Separator; -pub use slider::*; -pub use spinner::*; -pub use text_edit::{TextBuffer, TextEdit}; +pub use self::{ + button::Button, + checkbox::Checkbox, + drag_value::DragValue, + hyperlink::{Hyperlink, Link}, + image::{paint_texture_at, Image, ImageFit, ImageOptions, ImageSize, ImageSource}, + image_button::ImageButton, + label::Label, + progress_bar::ProgressBar, + radio_button::RadioButton, + selected_label::SelectableLabel, + separator::Separator, + slider::{Slider, SliderOrientation}, + spinner::Spinner, + text_edit::{TextBuffer, TextEdit}, +}; // ---------------------------------------------------------------------------- diff --git a/crates/egui/src/widgets/radio_button.rs b/crates/egui/src/widgets/radio_button.rs new file mode 100644 index 00000000000..ccbe785f6ab --- /dev/null +++ b/crates/egui/src/widgets/radio_button.rs @@ -0,0 +1,107 @@ +use crate::*; + +/// One out of several alternatives, either selected or not. +/// +/// Usually you'd use [`Ui::radio_value`] or [`Ui::radio`] instead. +/// +/// ``` +/// # egui::__run_test_ui(|ui| { +/// #[derive(PartialEq)] +/// enum Enum { First, Second, Third } +/// let mut my_enum = Enum::First; +/// +/// ui.radio_value(&mut my_enum, Enum::First, "First"); +/// +/// // is equivalent to: +/// +/// if ui.add(egui::RadioButton::new(my_enum == Enum::First, "First")).clicked() { +/// my_enum = Enum::First +/// } +/// # }); +/// ``` +#[must_use = "You should put this widget in an ui with `ui.add(widget);`"] +pub struct RadioButton { + checked: bool, + text: WidgetText, +} + +impl RadioButton { + pub fn new(checked: bool, text: impl Into) -> Self { + Self { + checked, + text: text.into(), + } + } +} + +impl Widget for RadioButton { + fn ui(self, ui: &mut Ui) -> Response { + let Self { checked, text } = self; + + let spacing = &ui.spacing(); + let icon_width = spacing.icon_width; + let icon_spacing = spacing.icon_spacing; + + let (galley, mut desired_size) = if text.is_empty() { + (None, vec2(icon_width, 0.0)) + } else { + let total_extra = vec2(icon_width + icon_spacing, 0.0); + + let wrap_width = ui.available_width() - total_extra.x; + let text = text.into_galley(ui, None, wrap_width, TextStyle::Button); + + let mut desired_size = total_extra + text.size(); + desired_size = desired_size.at_least(spacing.interact_size); + + (Some(text), desired_size) + }; + + desired_size = desired_size.at_least(Vec2::splat(spacing.interact_size.y)); + desired_size.y = desired_size.y.max(icon_width); + let (rect, response) = ui.allocate_exact_size(desired_size, Sense::click()); + + response.widget_info(|| { + WidgetInfo::selected( + WidgetType::RadioButton, + checked, + galley.as_ref().map_or("", |x| x.text()), + ) + }); + + if ui.is_rect_visible(rect) { + // let visuals = ui.style().interact_selectable(&response, checked); // too colorful + let visuals = ui.style().interact(&response); + + let (small_icon_rect, big_icon_rect) = ui.spacing().icon_rectangles(rect); + + let painter = ui.painter(); + + painter.add(epaint::CircleShape { + center: big_icon_rect.center(), + radius: big_icon_rect.width() / 2.0 + visuals.expansion, + fill: visuals.bg_fill, + stroke: visuals.bg_stroke, + }); + + if checked { + painter.add(epaint::CircleShape { + center: small_icon_rect.center(), + radius: small_icon_rect.width() / 3.0, + fill: visuals.fg_stroke.color, // Intentional to use stroke and not fill + // fill: ui.visuals().selection.stroke.color, // too much color + stroke: Default::default(), + }); + } + + if let Some(galley) = galley { + let text_pos = pos2( + rect.min.x + icon_width + icon_spacing, + rect.center().y - 0.5 * galley.size().y, + ); + ui.painter().galley(text_pos, galley, visuals.text_color()); + } + } + + response + } +} From d3c6895443052232109a332c43925796128f4abe Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 30 Mar 2024 16:22:16 +0100 Subject: [PATCH 068/134] eframe: Correctly identify if browser tab has focus (#4280) `input.focus` was often wrong on web --- crates/eframe/src/web/backend.rs | 4 ++- crates/eframe/src/web/events.rs | 49 +++++++++++++++++++++----------- 2 files changed, 35 insertions(+), 18 deletions(-) diff --git a/crates/eframe/src/web/backend.rs b/crates/eframe/src/web/backend.rs index 041fe42028a..74853abe9fb 100644 --- a/crates/eframe/src/web/backend.rs +++ b/crates/eframe/src/web/backend.rs @@ -36,8 +36,10 @@ impl WebInput { raw_input } + /// On alt-tab and similar. pub fn on_web_page_focus_change(&mut self, focused: bool) { - self.raw.modifiers = egui::Modifiers::default(); + // log::debug!("on_web_page_focus_change: {focused}"); + self.raw.modifiers = egui::Modifiers::default(); // Avoid sticky modifier keys on alt-tab: self.raw.focused = focused; self.raw.events.push(egui::Event::WindowFocused(focused)); self.latest_touch_pos = None; diff --git a/crates/eframe/src/web/events.rs b/crates/eframe/src/web/events.rs index 224ae3ac0db..f7234d76fad 100644 --- a/crates/eframe/src/web/events.rs +++ b/crates/eframe/src/web/events.rs @@ -50,24 +50,21 @@ fn paint_if_needed(runner: &mut AppRunner) { pub(crate) fn install_document_events(runner_ref: &WebRunner) -> Result<(), JsValue> { let document = web_sys::window().unwrap().document().unwrap(); - { - // Avoid sticky modifier keys on alt-tab: - for event_name in ["blur", "focus"] { - let closure = move |_event: web_sys::MouseEvent, runner: &mut AppRunner| { - let has_focus = event_name == "focus"; - - if !has_focus { - // We lost focus - good idea to save - runner.save(); - } + for event_name in ["blur", "focus"] { + let closure = move |_event: web_sys::MouseEvent, runner: &mut AppRunner| { + // log::debug!("{event_name:?}"); + let has_focus = event_name == "focus"; + + if !has_focus { + // We lost focus - good idea to save + runner.save(); + } - runner.input.on_web_page_focus_change(has_focus); - runner.egui_ctx().request_repaint(); - // log::debug!("{event_name:?}"); - }; + runner.input.on_web_page_focus_change(has_focus); + runner.egui_ctx().request_repaint(); + }; - runner_ref.add_event_listener(&document, event_name, closure)?; - } + runner_ref.add_event_listener(&document, event_name, closure)?; } runner_ref.add_event_listener( @@ -228,13 +225,31 @@ pub(crate) fn install_document_events(runner_ref: &WebRunner) -> Result<(), JsVa pub(crate) fn install_window_events(runner_ref: &WebRunner) -> Result<(), JsValue> { let window = web_sys::window().unwrap(); + for event_name in ["blur", "focus"] { + let closure = move |_event: web_sys::MouseEvent, runner: &mut AppRunner| { + // log::debug!("{event_name:?}"); + let has_focus = event_name == "focus"; + + if !has_focus { + // We lost focus - good idea to save + runner.save(); + } + + runner.input.on_web_page_focus_change(has_focus); + runner.egui_ctx().request_repaint(); + }; + + runner_ref.add_event_listener(&window, event_name, closure)?; + } + // Save-on-close runner_ref.add_event_listener(&window, "onbeforeunload", |_: web_sys::Event, runner| { runner.save(); })?; for event_name in &["load", "pagehide", "pageshow", "resize"] { - runner_ref.add_event_listener(&window, event_name, |_: web_sys::Event, runner| { + runner_ref.add_event_listener(&window, event_name, move |_: web_sys::Event, runner| { + // log::debug!("{event_name:?}"); runner.needs_repaint.repaint_asap(); })?; } From 3b147c066b006431fe57a5259b5a9f6c2453b475 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 30 Mar 2024 16:22:28 +0100 Subject: [PATCH 069/134] Implement blinking text cursor in `TextEdit` (#4279) On by default. Can be set with `style.text_cursor.blink`. * Closes https://github.com/emilk/egui/pull/4121 --- crates/egui/src/style.rs | 102 ++++++++++++++++--- crates/egui/src/text_selection/visuals.rs | 36 ++++++- crates/egui/src/widgets/text_edit/builder.rs | 44 +++++--- crates/egui/src/widgets/text_edit/state.rs | 5 + 4 files changed, 155 insertions(+), 32 deletions(-) diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index 1523345f9b8..df6ae55c4c5 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -647,6 +647,39 @@ pub struct Interaction { pub multi_widget_text_select: bool, } +/// Look and feel of the text cursor. +#[derive(Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr(feature = "serde", serde(default))] +pub struct TextCursorStyle { + /// The color and width of the text cursor + pub stroke: Stroke, + + /// Show where the text cursor would be if you clicked? + pub preview: bool, + + /// Should the cursor blink? + pub blink: bool, + + /// When blinking, this is how long the cursor is visible. + pub on_duration: f32, + + /// When blinking, this is how long the cursor is invisible. + pub off_duration: f32, +} + +impl Default for TextCursorStyle { + fn default() -> Self { + Self { + stroke: Stroke::new(2.0, Color32::from_rgb(192, 222, 255)), // Dark mode + preview: false, + blink: true, + on_duration: 0.5, + off_duration: 0.5, + } + } +} + /// Controls the visual style (colors etc) of egui. /// /// You can change the visuals of a [`Ui`] with [`Ui::visuals_mut`] @@ -722,11 +755,8 @@ pub struct Visuals { pub resize_corner_size: f32, - /// The color and width of the text cursor - pub text_cursor: Stroke, - - /// show where the text cursor would be if you clicked - pub text_cursor_preview: bool, + /// How the text cursor acts. + pub text_cursor: TextCursorStyle, /// Allow child widgets to be just on the border and still have a stroke with some thickness pub clip_rect_margin: f32, @@ -1094,8 +1124,7 @@ impl Visuals { resize_corner_size: 12.0, - text_cursor: Stroke::new(2.0, Color32::from_rgb(192, 222, 255)), - text_cursor_preview: false, + text_cursor: Default::default(), clip_rect_margin: 3.0, // should be at least half the size of the widest frame stroke + max WidgetVisuals::expansion button_frame: true, @@ -1146,7 +1175,10 @@ impl Visuals { color: Color32::from_black_alpha(25), }, - text_cursor: Stroke::new(2.0, Color32::from_rgb(0, 83, 125)), + text_cursor: TextCursorStyle { + stroke: Stroke::new(2.0, Color32::from_rgb(0, 83, 125)), + ..Default::default() + }, ..Self::dark() } @@ -1737,8 +1769,9 @@ impl Visuals { popup_shadow, resize_corner_size, + text_cursor, - text_cursor_preview, + clip_rect_margin, button_frame, collapsing_header_frame, @@ -1834,16 +1867,14 @@ impl Visuals { ); ui_color(ui, hyperlink_color, "hyperlink_color"); + }); - ui.horizontal(|ui| { - ui.label("Text cursor"); - ui.add(text_cursor); - }); + ui.collapsing("Text cursor", |ui| { + text_cursor.ui(ui); }); ui.collapsing("Misc", |ui| { ui.add(Slider::new(resize_corner_size, 0.0..=20.0).text("resize_corner_size")); - ui.checkbox(text_cursor_preview, "Preview text cursor on hover"); ui.add(Slider::new(clip_rect_margin, 0.0..=20.0).text("clip_rect_margin")); ui.checkbox(button_frame, "Button has a frame"); @@ -1887,6 +1918,49 @@ impl Visuals { } } +impl TextCursorStyle { + fn ui(&mut self, ui: &mut Ui) { + let Self { + stroke, + preview, + blink, + on_duration, + off_duration, + } = self; + + ui.horizontal(|ui| { + ui.label("Stroke"); + ui.add(stroke); + }); + + ui.checkbox(preview, "Preview text cursor on hover"); + + ui.checkbox(blink, "Blink"); + + if *blink { + Grid::new("cursor_blink").show(ui, |ui| { + ui.label("On time"); + ui.add( + DragValue::new(on_duration) + .speed(0.1) + .clamp_range(0.0..=2.0) + .suffix(" s"), + ); + ui.end_row(); + + ui.label("Off time"); + ui.add( + DragValue::new(off_duration) + .speed(0.1) + .clamp_range(0.0..=2.0) + .suffix(" s"), + ); + ui.end_row(); + }); + } + } +} + #[cfg(debug_assertions)] impl DebugOptions { pub fn ui(&mut self, ui: &mut crate::Ui) { diff --git a/crates/egui/src/text_selection/visuals.rs b/crates/egui/src/text_selection/visuals.rs index ca85e59ef66..4fc8af0abd4 100644 --- a/crates/egui/src/text_selection/visuals.rs +++ b/crates/egui/src/text_selection/visuals.rs @@ -51,8 +51,10 @@ pub fn paint_text_selection( } /// Paint one end of the selection, e.g. the primary cursor. -pub fn paint_cursor(painter: &Painter, visuals: &Visuals, cursor_rect: Rect) { - let stroke = visuals.text_cursor; +/// +/// This will never blink. +pub fn paint_cursor_end(painter: &Painter, visuals: &Visuals, cursor_rect: Rect) { + let stroke = visuals.text_cursor.stroke; let top = cursor_rect.center_top(); let bottom = cursor_rect.center_bottom(); @@ -73,3 +75,33 @@ pub fn paint_cursor(painter: &Painter, visuals: &Visuals, cursor_rect: Rect) { ); } } + +/// Paint one end of the selection, e.g. the primary cursor, with blinking (if enabled). +pub fn paint_text_cursor( + ui: &mut Ui, + painter: &Painter, + primary_cursor_rect: Rect, + time_since_last_edit: f64, +) { + if ui.visuals().text_cursor.blink { + let on_duration = ui.visuals().text_cursor.on_duration; + let off_duration = ui.visuals().text_cursor.off_duration; + let total_duration = on_duration + off_duration; + + let time_in_cycle = (time_since_last_edit % (total_duration as f64)) as f32; + + let wake_in = if time_in_cycle < on_duration { + // Cursor is visible + paint_cursor_end(painter, ui.visuals(), primary_cursor_rect); + on_duration - time_in_cycle + } else { + // Cursor is not visible + total_duration - time_in_cycle + }; + + ui.ctx() + .request_repaint_after(std::time::Duration::from_secs_f32(wake_in)); + } else { + paint_cursor_end(painter, ui.visuals(), primary_cursor_rect); + } +} diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index e320a1ae843..e0cf2d012f1 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -6,9 +6,7 @@ use crate::{ os::OperatingSystem, output::OutputEvent, text_selection::{ - text_cursor_state::cursor_rect, - visuals::{paint_cursor, paint_text_selection}, - CCursorRange, CursorRange, + text_cursor_state::cursor_rect, visuals::paint_text_selection, CCursorRange, CursorRange, }, *, }; @@ -544,14 +542,14 @@ impl<'t> TextEdit<'t> { let cursor_at_pointer = galley.cursor_from_pos(pointer_pos - rect.min + singleline_offset); - if ui.visuals().text_cursor_preview + if ui.visuals().text_cursor.preview && response.hovered() && ui.input(|i| i.pointer.is_moving()) { - // preview: + // text cursor preview: let cursor_rect = cursor_rect(rect.min, &galley, &cursor_at_pointer, row_height); - paint_cursor(&painter, ui.visuals(), cursor_rect); + text_selection::visuals::paint_cursor_end(&painter, ui.visuals(), cursor_rect); } let is_being_dragged = ui.ctx().is_being_dragged(response.id); @@ -683,18 +681,32 @@ impl<'t> TextEdit<'t> { ui.scroll_to_rect(primary_cursor_rect, None); } - if text.is_mutable() { - paint_cursor(&painter, ui.visuals(), primary_cursor_rect); + if text.is_mutable() && interactive { + let now = ui.ctx().input(|i| i.time); + if response.changed || selection_changed { + state.last_edit_time = now; + } - if interactive { - // For IME, so only set it when text is editable and visible! - ui.ctx().output_mut(|o| { - o.ime = Some(crate::output::IMEOutput { - rect, - cursor_rect: primary_cursor_rect, - }); - }); + // Only show (and blink) cursor if the egui viewport has focus. + // This is for two reasons: + // * Don't give the impression that the user can type into a window without focus + // * Don't repaint the ui because of a blinking cursor in an app that is not in focus + if ui.ctx().input(|i| i.focused) { + text_selection::visuals::paint_text_cursor( + ui, + &painter, + primary_cursor_rect, + now - state.last_edit_time, + ); } + + // For IME, so only set it when text is editable and visible! + ui.ctx().output_mut(|o| { + o.ime = Some(crate::output::IMEOutput { + rect, + cursor_rect: primary_cursor_rect, + }); + }); } } } diff --git a/crates/egui/src/widgets/text_edit/state.rs b/crates/egui/src/widgets/text_edit/state.rs index 8901fd56f27..d0334da3213 100644 --- a/crates/egui/src/widgets/text_edit/state.rs +++ b/crates/egui/src/widgets/text_edit/state.rs @@ -51,6 +51,11 @@ pub struct TextEditState { // Visual offset when editing singleline text bigger than the width. #[cfg_attr(feature = "serde", serde(skip))] pub(crate) singleline_offset: f32, + + /// When did the user last press a key? + /// Used to pause the cursor animation when typing. + #[cfg_attr(feature = "serde", serde(skip))] + pub(crate) last_edit_time: f64, } impl TextEditState { From 33221bd4dda712159accf97efef220ea73b13634 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E7=9A=93?= Date: Sat, 30 Mar 2024 12:14:58 -0400 Subject: [PATCH 070/134] Fix continuous repaint on Wayland when TextEdit is focused or IME output is not None (#4269) * Closes #4254 Changes egui-winit so that it calls `window.set_ime_cursor_area` when the IME rect changes or the user interacts with the application instead of calling it every time the app is rendered. This works around a winit bug that causes the app to continuously repaint under certain circumstances on Wayland. Tested on Wayland and on X11 using the text edit in the egui_demo_app - no changes in IME functionality as far as I can tell. Untested on non-Linux platforms. --------- Co-authored-by: Emil Ernerfeldt --- crates/egui-winit/src/lib.rs | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 41db04952e5..3d8e9184fdc 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -99,6 +99,7 @@ pub struct State { accesskit: Option, allow_ime: bool, + ime_rect_px: Option, } impl State { @@ -139,6 +140,7 @@ impl State { accesskit: None, allow_ime: false, + ime_rect_px: None, }; slf.egui_input @@ -817,19 +819,26 @@ impl State { } if let Some(ime) = ime { - let rect = ime.rect; let pixels_per_point = pixels_per_point(&self.egui_ctx, window); - crate::profile_scope!("set_ime_cursor_area"); - window.set_ime_cursor_area( - winit::dpi::PhysicalPosition { - x: pixels_per_point * rect.min.x, - y: pixels_per_point * rect.min.y, - }, - winit::dpi::PhysicalSize { - width: pixels_per_point * rect.width(), - height: pixels_per_point * rect.height(), - }, - ); + let ime_rect_px = pixels_per_point * ime.rect; + if self.ime_rect_px != Some(ime_rect_px) + || self.egui_ctx.input(|i| !i.events.is_empty()) + { + self.ime_rect_px = Some(ime_rect_px); + crate::profile_scope!("set_ime_cursor_area"); + window.set_ime_cursor_area( + winit::dpi::PhysicalPosition { + x: ime_rect_px.min.x, + y: ime_rect_px.min.y, + }, + winit::dpi::PhysicalSize { + width: ime_rect_px.width(), + height: ime_rect_px.height(), + }, + ); + } + } else { + self.ime_rect_px = None; } #[cfg(feature = "accesskit")] From 1354c3e19a6d3b1b43fbe02c987d100fdcf6ec98 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 30 Mar 2024 17:51:44 +0100 Subject: [PATCH 071/134] Make the code example demo narrow enough to fit on mobile (#4281) I think it is a good example, and so I want to open it by default, but for that it needs to work on mobile. Hopefully this doesn't make it too cryptic image --- crates/egui_demo_app/src/backend_panel.rs | 1 + crates/egui_demo_lib/src/demo/code_example.rs | 106 ++++++++++-------- .../src/demo/demo_app_windows.rs | 9 ++ 3 files changed, 71 insertions(+), 45 deletions(-) diff --git a/crates/egui_demo_app/src/backend_panel.rs b/crates/egui_demo_app/src/backend_panel.rs index 51225587134..2176d57a8b6 100644 --- a/crates/egui_demo_app/src/backend_panel.rs +++ b/crates/egui_demo_app/src/backend_panel.rs @@ -305,6 +305,7 @@ fn integration_ui(ui: &mut egui::Ui, _frame: &mut eframe::Frame) { Some(egui::vec2(375.0, 667.0)), "📱 iPhone SE 2nd Gen", ); + ui.selectable_value(&mut size, Some(egui::vec2(393.0, 852.0)), "📱 iPhone 15"); ui.selectable_value( &mut size, Some(egui::vec2(1280.0, 720.0)), diff --git a/crates/egui_demo_lib/src/demo/code_example.rs b/crates/egui_demo_lib/src/demo/code_example.rs index 6786e55a14c..8e109ca4da0 100644 --- a/crates/egui_demo_lib/src/demo/code_example.rs +++ b/crates/egui_demo_lib/src/demo/code_example.rs @@ -15,78 +15,64 @@ impl Default for CodeExample { impl CodeExample { fn samples_in_grid(&mut self, ui: &mut egui::Ui) { - show_code(ui, r#"ui.heading("Code samples");"#); - ui.heading("Code samples"); + // Note: we keep the code narrow so that the example fits on a mobile screen. + + let Self { name, age } = self; // for brevity later on + + show_code(ui, r#"ui.heading("Example");"#); + ui.heading("Example"); ui.end_row(); show_code( ui, r#" - // Putting things on the same line using ui.horizontal: ui.horizontal(|ui| { - ui.label("Your name: "); - ui.text_edit_singleline(&mut self.name); + ui.label("Name"); + ui.text_edit_singleline(name); });"#, ); // Putting things on the same line using ui.horizontal: ui.horizontal(|ui| { - ui.label("Your name: "); - ui.text_edit_singleline(&mut self.name); + ui.label("Name"); + ui.text_edit_singleline(name); }); ui.end_row(); show_code( ui, - r#"ui.add(egui::Slider::new(&mut self.age, 0..=120).text("age"));"#, + r#" + ui.add( + egui::DragValue::new(age) + .clamp_range(0..=120) + .suffix(" years"), + );"#, + ); + ui.add( + egui::DragValue::new(age) + .clamp_range(0..=120) + .suffix(" years"), ); - ui.add(egui::Slider::new(&mut self.age, 0..=120).text("age")); ui.end_row(); show_code( ui, r#" if ui.button("Increment").clicked() { - self.age += 1; + *age += 1; }"#, ); if ui.button("Increment").clicked() { - self.age += 1; + *age += 1; } ui.end_row(); - show_code( - ui, - r#"ui.label(format!("Hello '{}', age {}", self.name, self.age));"#, - ); - ui.label(format!("Hello '{}', age {}", self.name, self.age)); + show_code(ui, r#"ui.label(format!("{name} is {age}"));"#); + ui.label(format!("{name} is {age}")); ui.end_row(); } -} - -impl super::Demo for CodeExample { - fn name(&self) -> &'static str { - "🖮 Code Example" - } - - fn show(&mut self, ctx: &egui::Context, open: &mut bool) { - use super::View; - egui::Window::new(self.name()) - .open(open) - .default_size([800.0, 400.0]) - .vscroll(false) - .hscroll(true) - .resizable([true, false]) - .show(ctx, |ui| self.ui(ui)); - } -} - -impl super::View for CodeExample { - fn ui(&mut self, ui: &mut egui::Ui) { - ui.vertical_centered(|ui| { - ui.add(crate::egui_github_link_file!()); - }); - crate::rust_view_ui( + fn code(&mut self, ui: &mut egui::Ui) { + show_code( ui, r" pub struct CodeExample { @@ -96,8 +82,8 @@ pub struct CodeExample { impl CodeExample { fn ui(&mut self, ui: &mut egui::Ui) { -" - .trim(), + // Saves us from writing `&mut self.name` etc + let Self { name, age } = self;", ); ui.horizontal(|ui| { @@ -109,14 +95,38 @@ impl CodeExample { egui::Grid::new("code_samples") .striped(true) .num_columns(2) - .min_col_width(16.0) - .spacing([16.0, 8.0]) .show(ui, |ui| { self.samples_in_grid(ui); }); }); crate::rust_view_ui(ui, " }\n}"); + } +} + +impl super::Demo for CodeExample { + fn name(&self) -> &'static str { + "🖮 Code Example" + } + + fn show(&mut self, ctx: &egui::Context, open: &mut bool) { + use super::View; + egui::Window::new(self.name()) + .open(open) + .min_width(375.0) + .default_size([390.0, 500.0]) + .scroll2(false) + .resizable([true, false]) + .show(ctx, |ui| self.ui(ui)); + } +} + +impl super::View for CodeExample { + fn ui(&mut self, ui: &mut egui::Ui) { + ui.scope(|ui| { + ui.spacing_mut().item_spacing = egui::vec2(8.0, 8.0); + self.code(ui); + }); ui.separator(); @@ -129,6 +139,12 @@ impl CodeExample { theme.ui(ui); theme.store_in_memory(ui.ctx()); }); + + ui.separator(); + + ui.vertical_centered(|ui| { + ui.add(crate::egui_github_link_file!()); + }); } } diff --git a/crates/egui_demo_lib/src/demo/demo_app_windows.rs b/crates/egui_demo_lib/src/demo/demo_app_windows.rs index 505e938d0d8..0910df4ef56 100644 --- a/crates/egui_demo_lib/src/demo/demo_app_windows.rs +++ b/crates/egui_demo_lib/src/demo/demo_app_windows.rs @@ -52,6 +52,15 @@ impl Default for Demos { impl Demos { pub fn from_demos(demos: Vec>) -> Self { let mut open = BTreeSet::new(); + + // Explains egui very well + open.insert( + super::code_example::CodeExample::default() + .name() + .to_owned(), + ); + + // Shows off the features open.insert( super::widget_gallery::WidgetGallery::default() .name() From 549b2432284d88cac7493b776e9437ede7f8efbc Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 30 Mar 2024 18:00:43 +0100 Subject: [PATCH 072/134] Rename `fn scroll2` to `fn scroll` (#4282) --- crates/egui/src/containers/scroll_area.rs | 10 ++++++++++ crates/egui/src/containers/window.rs | 12 +++++++++++- crates/egui_demo_lib/src/demo/code_example.rs | 2 +- crates/egui_demo_lib/src/demo/tests.rs | 2 +- crates/egui_demo_lib/src/demo/window_options.rs | 2 +- 5 files changed, 24 insertions(+), 4 deletions(-) diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index c35cca34225..9c05068f60f 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -337,6 +337,16 @@ impl ScrollArea { } /// Turn on/off scrolling on the horizontal/vertical axes. + /// + /// You can pass in `false`, `true`, `[false, true]` etc. + #[inline] + pub fn scroll(mut self, scroll_enabled: impl Into) -> Self { + self.scroll_enabled = scroll_enabled.into(); + self + } + + /// Turn on/off scrolling on the horizontal/vertical axes. + #[deprecated = "Renamed to `scroll`"] #[inline] pub fn scroll2(mut self, scroll_enabled: impl Into) -> Self { self.scroll_enabled = scroll_enabled.into(); diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index 619675ca8a5..2b266dbd8ff 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -328,9 +328,19 @@ impl<'open> Window<'open> { } /// Enable/disable horizontal/vertical scrolling. `false` by default. + /// + /// You can pass in `false`, `true`, `[false, true]` etc. + #[inline] + pub fn scroll(mut self, scroll: impl Into) -> Self { + self.scroll = self.scroll.scroll(scroll); + self + } + + /// Enable/disable horizontal/vertical scrolling. `false` by default. + #[deprecated = "Renamed to `scroll`"] #[inline] pub fn scroll2(mut self, scroll: impl Into) -> Self { - self.scroll = self.scroll.scroll2(scroll); + self.scroll = self.scroll.scroll(scroll); self } diff --git a/crates/egui_demo_lib/src/demo/code_example.rs b/crates/egui_demo_lib/src/demo/code_example.rs index 8e109ca4da0..2b09b557be3 100644 --- a/crates/egui_demo_lib/src/demo/code_example.rs +++ b/crates/egui_demo_lib/src/demo/code_example.rs @@ -115,7 +115,7 @@ impl super::Demo for CodeExample { .open(open) .min_width(375.0) .default_size([390.0, 500.0]) - .scroll2(false) + .scroll(false) .resizable([true, false]) .show(ctx, |ui| self.ui(ui)); } diff --git a/crates/egui_demo_lib/src/demo/tests.rs b/crates/egui_demo_lib/src/demo/tests.rs index a29e37fe0bc..44e355d0e8e 100644 --- a/crates/egui_demo_lib/src/demo/tests.rs +++ b/crates/egui_demo_lib/src/demo/tests.rs @@ -372,7 +372,7 @@ impl super::Demo for InputTest { .default_width(800.0) .open(open) .resizable(true) - .scroll2(false) + .scroll(false) .show(ctx, |ui| { use super::View as _; self.ui(ui); diff --git a/crates/egui_demo_lib/src/demo/window_options.rs b/crates/egui_demo_lib/src/demo/window_options.rs index 54c77988e75..7d4241dd38e 100644 --- a/crates/egui_demo_lib/src/demo/window_options.rs +++ b/crates/egui_demo_lib/src/demo/window_options.rs @@ -67,7 +67,7 @@ impl super::Demo for WindowOptions { .constrain(constrain) .collapsible(collapsible) .title_bar(title_bar) - .scroll2(scroll2) + .scroll(scroll2) .enabled(enabled); if closable { window = window.open(open); From e03ea2e17d73b3597a8dae8c4d833d40b1413a0c Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 30 Mar 2024 18:38:59 +0100 Subject: [PATCH 073/134] eframe: Early-out from context switching the `glow` backend (#4284) * Closes https://github.com/emilk/egui/issues/4173 --- crates/eframe/src/native/glow_integration.rs | 31 +++++++------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index 8dd0f05f6e2..ba0b4d8fbd2 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -836,6 +836,13 @@ fn change_gl_context( ) { crate::profile_function!(); + if let Some(current_gl_context) = current_gl_context { + crate::profile_scope!("is_current"); + if gl_surface.is_current(current_gl_context) { + return; // Early-out to save a lot of time. + } + } + let not_current = { crate::profile_scope!("make_not_current"); current_gl_context @@ -844,6 +851,7 @@ fn change_gl_context( .make_not_current() .unwrap() }; + crate::profile_scope!("make_current"); *current_gl_context = Some(not_current.make_current(gl_surface).unwrap()); } @@ -1178,15 +1186,7 @@ impl GlutinWindowContext { if let Some(viewport) = self.viewports.get(&viewport_id) { if let Some(gl_surface) = &viewport.gl_surface { - self.current_gl_context = Some( - self.current_gl_context - .take() - .unwrap() - .make_not_current() - .unwrap() - .make_current(gl_surface) - .unwrap(), - ); + change_gl_context(&mut self.current_gl_context, gl_surface); gl_surface.resize( self.current_gl_context .as_ref() @@ -1436,18 +1436,7 @@ fn render_immediate_viewport( let screen_size_in_pixels: [u32; 2] = window.inner_size().into(); - { - crate::profile_function!("context-switch"); - *current_gl_context = Some( - current_gl_context - .take() - .unwrap() - .make_not_current() - .unwrap() - .make_current(gl_surface) - .unwrap(), - ); - } + change_gl_context(current_gl_context, gl_surface); let current_gl_context = current_gl_context.as_ref().unwrap(); From ab720ce900161180b3a4ee5d1eb5cc3867d9cca2 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 30 Mar 2024 18:39:05 +0100 Subject: [PATCH 074/134] Change `Frame::multiply_with_opacity` to multiply in gamma space (#4283) This produces a more perceptually even change when used in animations, like when fading out a closed window --- crates/ecolor/src/color32.rs | 2 +- crates/egui/src/containers/frame.rs | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/crates/ecolor/src/color32.rs b/crates/ecolor/src/color32.rs index 0b60176f5cd..a2f06294819 100644 --- a/crates/ecolor/src/color32.rs +++ b/crates/ecolor/src/color32.rs @@ -212,7 +212,7 @@ impl Color32 { /// Multiply with 0.5 to make color half as opaque in linear space. /// /// This is using linear space, which is not perceptually even. - /// You may want to use [`Self::gamma_multiply`] instead. + /// You likely want to use [`Self::gamma_multiply`] instead. #[inline] pub fn linear_multiply(self, factor: f32) -> Self { crate::ecolor_assert!(0.0 <= factor && factor <= 1.0); diff --git a/crates/egui/src/containers/frame.rs b/crates/egui/src/containers/frame.rs index b982cfd0b03..82ef47770a7 100644 --- a/crates/egui/src/containers/frame.rs +++ b/crates/egui/src/containers/frame.rs @@ -196,11 +196,15 @@ impl Frame { self } + /// Opacity multiplier in gamma space. + /// + /// For instance, multiplying with `0.5` + /// will make the frame half transparent. #[inline] pub fn multiply_with_opacity(mut self, opacity: f32) -> Self { - self.fill = self.fill.linear_multiply(opacity); - self.stroke.color = self.stroke.color.linear_multiply(opacity); - self.shadow.color = self.shadow.color.linear_multiply(opacity); + self.fill = self.fill.gamma_multiply(opacity); + self.stroke.color = self.stroke.color.gamma_multiply(opacity); + self.shadow.color = self.shadow.color.gamma_multiply(opacity); self } } From 2ee9d30d6e9879460d771c480a64a393f29e5241 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 30 Mar 2024 19:12:54 +0100 Subject: [PATCH 075/134] Make it easier to tweak text colors in settings --- crates/egui/src/style.rs | 72 ++++++++++++++----------- crates/egui/src/widgets/color_picker.rs | 2 +- 2 files changed, 42 insertions(+), 32 deletions(-) diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index df6ae55c4c5..9f9aac0534a 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -1799,6 +1799,37 @@ impl Visuals { .on_hover_text("Background of plots and paintings"); }); + ui.collapsing("Text color", |ui| { + ui_text_color(ui, &mut widgets.noninteractive.fg_stroke.color, "Label"); + ui_text_color( + ui, + &mut widgets.inactive.fg_stroke.color, + "Unhovered button", + ); + ui_text_color(ui, &mut widgets.hovered.fg_stroke.color, "Hovered button"); + ui_text_color(ui, &mut widgets.active.fg_stroke.color, "Clicked button"); + + ui_text_color(ui, warn_fg_color, RichText::new("Warnings")); + ui_text_color(ui, error_fg_color, RichText::new("Errors")); + + ui_text_color(ui, hyperlink_color, "hyperlink_color"); + + ui_color(ui, code_bg_color, RichText::new("Code background").code()).on_hover_ui( + |ui| { + ui.horizontal(|ui| { + ui.spacing_mut().item_spacing.x = 0.0; + ui.label("For monospaced inlined text "); + ui.code("like this"); + ui.label("."); + }); + }, + ); + }); + + ui.collapsing("Text cursor", |ui| { + text_cursor.ui(ui); + }); + ui.collapsing("Window", |ui| { Grid::new("window") .num_columns(2) @@ -1844,35 +1875,6 @@ impl Visuals { ui.collapsing("Widgets", |ui| widgets.ui(ui)); ui.collapsing("Selection", |ui| selection.ui(ui)); - ui.collapsing("Other colors", |ui| { - ui.horizontal(|ui| { - ui_color( - ui, - &mut widgets.noninteractive.fg_stroke.color, - "Text color", - ); - ui_color(ui, warn_fg_color, RichText::new("Warnings")); - ui_color(ui, error_fg_color, RichText::new("Errors")); - }); - - ui_color(ui, code_bg_color, RichText::new("Code background").code()).on_hover_ui( - |ui| { - ui.horizontal(|ui| { - ui.spacing_mut().item_spacing.x = 0.0; - ui.label("For monospaced inlined text "); - ui.code("like this"); - ui.label("."); - }); - }, - ); - - ui_color(ui, hyperlink_color, "hyperlink_color"); - }); - - ui.collapsing("Text cursor", |ui| { - text_cursor.ui(ui); - }); - ui.collapsing("Misc", |ui| { ui.add(Slider::new(resize_corner_size, 0.0..=20.0).text("resize_corner_size")); ui.add(Slider::new(clip_rect_margin, 0.0..=20.0).text("clip_rect_margin")); @@ -2025,14 +2027,22 @@ fn two_drag_values(value: &mut Vec2, range: std::ops::RangeInclusive) -> im } } -fn ui_color(ui: &mut Ui, srgba: &mut Color32, label: impl Into) -> Response { +fn ui_color(ui: &mut Ui, color: &mut Color32, label: impl Into) -> Response { ui.horizontal(|ui| { - ui.color_edit_button_srgba(srgba); + ui.color_edit_button_srgba(color); ui.label(label); }) .response } +fn ui_text_color(ui: &mut Ui, color: &mut Color32, label: impl Into) -> Response { + ui.horizontal(|ui| { + ui.color_edit_button_srgba(color); + ui.label(label.into().color(*color)); + }) + .response +} + impl HandleShape { pub fn ui(&mut self, ui: &mut Ui) { ui.horizontal(|ui| { diff --git a/crates/egui/src/widgets/color_picker.rs b/crates/egui/src/widgets/color_picker.rs index 8426adee968..6935de4536d 100644 --- a/crates/egui/src/widgets/color_picker.rs +++ b/crates/egui/src/widgets/color_picker.rs @@ -93,7 +93,7 @@ fn color_button(ui: &mut Ui, color: Color32, open: bool) -> Response { show_color_at(ui.painter(), color, rect); - let rounding = visuals.rounding.at_most(2.0); + let rounding = visuals.rounding.at_most(2.0); // Can't do more rounding because the background grid doesn't do any rounding ui.painter() .rect_stroke(rect, rounding, (2.0, visuals.bg_fill)); // fill is intentional, because default style has no border } From 5a0a1e96e05d8429f98bc9741a1036484e6a29b2 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 30 Mar 2024 19:33:19 +0100 Subject: [PATCH 076/134] Remove a bunch of `unwrap()` (#4285) The fewer unwraps, the fewer panics --- crates/eframe/src/native/glow_integration.rs | 40 ++++++++++++-------- crates/eframe/src/native/wgpu_integration.rs | 20 +++++----- crates/egui_glow/examples/pure_glow.rs | 9 +++-- 3 files changed, 40 insertions(+), 29 deletions(-) diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index ba0b4d8fbd2..65e2094b886 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -7,7 +7,7 @@ #![allow(clippy::arc_with_non_send_sync)] // glow::Context was accidentally non-Sync in glow 0.13, but that will be fixed in future releases of glow: https://github.com/grovesNL/glow/commit/c4a5f7151b9b4bbb380faa06ec27415235d1bf7e -use std::{cell::RefCell, rc::Rc, sync::Arc, time::Instant}; +use std::{cell::RefCell, num::NonZeroU32, rc::Rc, sync::Arc, time::Instant}; use glutin::{ config::GlConfig, @@ -22,9 +22,9 @@ use winit::{ }; use egui::{ - epaint::ahash::HashMap, DeferredViewportUiCallback, ImmediateViewport, NumExt as _, - ViewportBuilder, ViewportClass, ViewportId, ViewportIdMap, ViewportIdPair, ViewportIdSet, - ViewportInfo, ViewportOutput, + epaint::ahash::HashMap, DeferredViewportUiCallback, ImmediateViewport, ViewportBuilder, + ViewportClass, ViewportId, ViewportIdMap, ViewportIdPair, ViewportIdSet, ViewportInfo, + ViewportOutput, }; #[cfg(feature = "accesskit")] use egui_winit::accesskit_winit; @@ -257,7 +257,7 @@ impl GlowWinitApp { #[cfg(feature = "accesskit")] { let event_loop_proxy = self.repaint_proxy.lock().clone(); - let viewport = glutin.viewports.get_mut(&ViewportId::ROOT).unwrap(); + let viewport = glutin.viewports.get_mut(&ViewportId::ROOT).unwrap(); // we always have a root if let Viewport { window: Some(window), egui_winit: Some(egui_winit), @@ -548,13 +548,17 @@ impl GlowWinitRunning { let (raw_input, viewport_ui_cb) = { let mut glutin = self.glutin.borrow_mut(); let egui_ctx = glutin.egui_ctx.clone(); - let viewport = glutin.viewports.get_mut(&viewport_id).unwrap(); + let Some(viewport) = glutin.viewports.get_mut(&viewport_id) else { + return EventResult::Wait; + }; let Some(window) = viewport.window.as_ref() else { return EventResult::Wait; }; egui_winit::update_viewport_info(&mut viewport.info, &egui_ctx, window); - let egui_winit = viewport.egui_winit.as_mut().unwrap(); + let Some(egui_winit) = viewport.egui_winit.as_mut() else { + return EventResult::Wait; + }; let mut raw_input = egui_winit.take_egui_input(window); let viewport_ui_cb = viewport.viewport_ui_cb.clone(); @@ -587,8 +591,12 @@ impl GlowWinitRunning { .. } = &mut *glutin; let viewport = &viewports[&viewport_id]; - let window = viewport.window.as_ref().unwrap(); - let gl_surface = viewport.gl_surface.as_ref().unwrap(); + let Some(window) = viewport.window.as_ref() else { + return EventResult::Wait; + }; + let Some(gl_surface) = viewport.gl_surface.as_ref() else { + return EventResult::Wait; + }; let screen_size_in_pixels: [u32; 2] = window.inner_size().into(); @@ -638,7 +646,9 @@ impl GlowWinitRunning { .. } = &mut *glutin; - let viewport = viewports.get_mut(&viewport_id).unwrap(); + let Some(viewport) = viewports.get_mut(&viewport_id) else { + return EventResult::Wait; + }; viewport.info.events.clear(); // they should have been processed let window = viewport.window.clone().unwrap(); let gl_surface = viewport.gl_surface.as_ref().unwrap(); @@ -877,7 +887,7 @@ impl GlutinWindowContext { crate::HardwareAcceleration::Off => Some(false), }; let swap_interval = if native_options.vsync { - glutin::surface::SwapInterval::Wait(std::num::NonZeroU32::new(1).unwrap()) + glutin::surface::SwapInterval::Wait(NonZeroU32::MIN) } else { glutin::surface::SwapInterval::DontWait }; @@ -1101,8 +1111,8 @@ impl GlutinWindowContext { // surface attributes let (width_px, height_px): (u32, u32) = window.inner_size().into(); - let width_px = std::num::NonZeroU32::new(width_px.at_least(1)).unwrap(); - let height_px = std::num::NonZeroU32::new(height_px.at_least(1)).unwrap(); + let width_px = NonZeroU32::new(width_px).unwrap_or(NonZeroU32::MIN); + let height_px = NonZeroU32::new(height_px).unwrap_or(NonZeroU32::MIN); let surface_attributes = { use rwh_05::HasRawWindowHandle as _; // glutin stuck on old version of raw-window-handle glutin::surface::SurfaceAttributesBuilder::::new() @@ -1181,8 +1191,8 @@ impl GlutinWindowContext { } fn resize(&mut self, viewport_id: ViewportId, physical_size: winit::dpi::PhysicalSize) { - let width_px = std::num::NonZeroU32::new(physical_size.width.at_least(1)).unwrap(); - let height_px = std::num::NonZeroU32::new(physical_size.height.at_least(1)).unwrap(); + let width_px = NonZeroU32::new(physical_size.width).unwrap_or(NonZeroU32::MIN); + let height_px = NonZeroU32::new(physical_size.height).unwrap_or(NonZeroU32::MIN); if let Some(viewport) = self.viewports.get(&viewport_id) { if let Some(gl_surface) = &viewport.gl_surface { diff --git a/crates/eframe/src/native/wgpu_integration.rs b/crates/eframe/src/native/wgpu_integration.rs index c17bba27afa..dd4debd8b7a 100644 --- a/crates/eframe/src/native/wgpu_integration.rs +++ b/crates/eframe/src/native/wgpu_integration.rs @@ -5,7 +5,7 @@ //! There is a bunch of improvements we could do, //! like removing a bunch of `unwraps`. -use std::{cell::RefCell, rc::Rc, sync::Arc, time::Instant}; +use std::{cell::RefCell, num::NonZeroU32, rc::Rc, sync::Arc, time::Instant}; use parking_lot::Mutex; use raw_window_handle::{HasDisplayHandle as _, HasWindowHandle as _}; @@ -437,13 +437,12 @@ impl WinitApp for WgpuWinitApp { self.init_run_state(egui_ctx, event_loop, storage, window, builder)? }; - EventResult::RepaintNow( - running.shared.borrow().viewports[&ViewportId::ROOT] - .window - .as_ref() - .unwrap() - .id(), - ) + let viewport = &running.shared.borrow().viewports[&ViewportId::ROOT]; + if let Some(window) = &viewport.window { + EventResult::RepaintNow(window.id()) + } else { + EventResult::Wait + } } winit::event::Event::Suspended => { @@ -615,7 +614,9 @@ impl WgpuWinitRunning { } } - let egui_winit = egui_winit.as_mut().unwrap(); + let Some(egui_winit) = egui_winit.as_mut() else { + return EventResult::Wait; + }; let mut raw_input = egui_winit.take_egui_input(window); integration.pre_update(); @@ -775,7 +776,6 @@ impl WgpuWinitRunning { // See: https://github.com/rust-windowing/winit/issues/208 // This solves an issue where the app would panic when minimizing on Windows. if let Some(viewport_id) = viewport_id { - use std::num::NonZeroU32; if let (Some(width), Some(height)) = ( NonZeroU32::new(physical_size.width), NonZeroU32::new(physical_size.height), diff --git a/crates/egui_glow/examples/pure_glow.rs b/crates/egui_glow/examples/pure_glow.rs index eab04af1c5f..3da382af6ec 100644 --- a/crates/egui_glow/examples/pure_glow.rs +++ b/crates/egui_glow/examples/pure_glow.rs @@ -4,6 +4,8 @@ #![allow(unsafe_code)] #![allow(clippy::arc_with_non_send_sync)] // glow::Context was accidentally non-Sync in glow 0.13, but that will be fixed in future releases of glow: https://github.com/grovesNL/glow/commit/c4a5f7151b9b4bbb380faa06ec27415235d1bf7e +use std::num::NonZeroU32; + use egui_winit::winit; /// The majority of `GlutinWindowContext` is taken from `eframe` @@ -19,7 +21,6 @@ impl GlutinWindowContext { // preferably add android support at the same time. #[allow(unsafe_code)] unsafe fn new(event_loop: &winit::event_loop::EventLoopWindowTarget) -> Self { - use egui::NumExt; use glutin::context::NotCurrentGlContext; use glutin::display::GetGlDisplay; use glutin::display::GlDisplay; @@ -87,8 +88,8 @@ impl GlutinWindowContext { .expect("failed to finalize glutin window") }); let (width, height): (u32, u32) = window.inner_size().into(); - let width = std::num::NonZeroU32::new(width.at_least(1)).unwrap(); - let height = std::num::NonZeroU32::new(height.at_least(1)).unwrap(); + let width = NonZeroU32::new(width).unwrap_or(NonZeroU32::MIN); + let height = NonZeroU32::new(height).unwrap_or(NonZeroU32::MIN); let surface_attributes = glutin::surface::SurfaceAttributesBuilder::::new() .build(window.raw_window_handle(), width, height); @@ -107,7 +108,7 @@ impl GlutinWindowContext { gl_surface .set_swap_interval( &gl_context, - glutin::surface::SwapInterval::Wait(std::num::NonZeroU32::new(1).unwrap()), + glutin::surface::SwapInterval::Wait(NonZeroU32::MIN), ) .unwrap(); From 21835c31760f0c3e03d0d3db1f44b00ecb955734 Mon Sep 17 00:00:00 2001 From: rustbasic <127506429+rustbasic@users.noreply.github.com> Date: Sun, 31 Mar 2024 04:09:28 +0900 Subject: [PATCH 077/134] Fix `ViewportCommand::InnerSize` not resizing viewport on Wayland (#4211) --- crates/eframe/src/native/glow_integration.rs | 93 ++++++------ crates/eframe/src/native/wgpu_integration.rs | 149 +++++++++++-------- crates/egui-winit/src/lib.rs | 116 ++++++++------- 3 files changed, 198 insertions(+), 160 deletions(-) diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index 65e2094b886..823e0b0201a 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -23,8 +23,7 @@ use winit::{ use egui::{ epaint::ahash::HashMap, DeferredViewportUiCallback, ImmediateViewport, ViewportBuilder, - ViewportClass, ViewportId, ViewportIdMap, ViewportIdPair, ViewportIdSet, ViewportInfo, - ViewportOutput, + ViewportClass, ViewportId, ViewportIdMap, ViewportIdPair, ViewportInfo, ViewportOutput, }; #[cfg(feature = "accesskit")] use egui_winit::accesskit_winit; @@ -103,6 +102,7 @@ struct Viewport { ids: ViewportIdPair, class: ViewportClass, builder: ViewportBuilder, + deferred_commands: Vec, info: ViewportInfo, screenshot_requested: bool, @@ -554,7 +554,7 @@ impl GlowWinitRunning { let Some(window) = viewport.window.as_ref() else { return EventResult::Wait; }; - egui_winit::update_viewport_info(&mut viewport.info, &egui_ctx, window); + egui_winit::update_viewport_info(&mut viewport.info, &egui_ctx, window, false); let Some(egui_winit) = viewport.egui_winit.as_mut() else { return EventResult::Wait; @@ -640,6 +640,8 @@ impl GlowWinitRunning { viewport_output, } = full_output; + glutin.remove_viewports_not_in(&viewport_output); + let GlutinWindowContext { viewports, current_gl_context, @@ -649,6 +651,7 @@ impl GlowWinitRunning { let Some(viewport) = viewports.get_mut(&viewport_id) else { return EventResult::Wait; }; + viewport.info.events.clear(); // they should have been processed let window = viewport.window.clone().unwrap(); let gl_surface = viewport.gl_surface.as_ref().unwrap(); @@ -715,7 +718,7 @@ impl GlowWinitRunning { } } - glutin.handle_viewport_output(event_loop, &integration.egui_ctx, viewport_output); + glutin.handle_viewport_output(event_loop, &integration.egui_ctx, &viewport_output); integration.report_frame_time(frame_timer.total_time_sec()); // don't count auto-save time as part of regular frame time @@ -998,8 +1001,7 @@ impl GlutinWindowContext { if let Some(window) = &window { viewport_from_window.insert(window.id(), ViewportId::ROOT); window_from_viewport.insert(ViewportId::ROOT, window.id()); - info.minimized = window.is_minimized(); - info.maximized = Some(window.is_maximized()); + egui_winit::update_viewport_info(&mut info, egui_ctx, window, true); } let mut viewports = ViewportIdMap::default(); @@ -1009,6 +1011,7 @@ impl GlutinWindowContext { ids: ViewportIdPair::ROOT, class: ViewportClass::Root, builder: viewport_builder, + deferred_commands: vec![], info, screenshot_requested: false, viewport_ui_cb: None, @@ -1090,8 +1093,8 @@ impl GlutinWindowContext { &window, &viewport.builder, ); - viewport.info.minimized = window.is_minimized(); - viewport.info.maximized = Some(window.is_maximized()); + + egui_winit::update_viewport_info(&mut viewport.info, &self.egui_ctx, &window, true); viewport.window.insert(Arc::new(window)) }; @@ -1212,16 +1215,27 @@ impl GlutinWindowContext { self.gl_config.display().get_proc_address(addr) } + pub(crate) fn remove_viewports_not_in( + &mut self, + viewport_output: &ViewportIdMap, + ) { + // GC old viewports + self.viewports + .retain(|id, _| viewport_output.contains_key(id)); + self.viewport_from_window + .retain(|_, id| viewport_output.contains_key(id)); + self.window_from_viewport + .retain(|id, _| viewport_output.contains_key(id)); + } + fn handle_viewport_output( &mut self, event_loop: &EventLoopWindowTarget, egui_ctx: &egui::Context, - viewport_output: ViewportIdMap, + viewport_output: &ViewportIdMap, ) { crate::profile_function!(); - let active_viewports_ids: ViewportIdSet = viewport_output.keys().copied().collect(); - for ( viewport_id, ViewportOutput { @@ -1229,58 +1243,60 @@ impl GlutinWindowContext { class, builder, viewport_ui_cb, - commands, + mut commands, repaint_delay: _, // ignored - we listened to the repaint callback instead }, - ) in viewport_output + ) in viewport_output.clone() { let ids = ViewportIdPair::from_self_and_parent(viewport_id, parent); let viewport = initialize_or_update_viewport( - egui_ctx, &mut self.viewports, ids, class, builder, viewport_ui_cb, - self.focused_viewport, ); if let Some(window) = &viewport.window { + let old_inner_size = window.inner_size(); + let is_viewport_focused = self.focused_viewport == Some(viewport_id); + viewport.deferred_commands.append(&mut commands); + egui_winit::process_viewport_commands( egui_ctx, &mut viewport.info, - commands, + std::mem::take(&mut viewport.deferred_commands), window, is_viewport_focused, &mut viewport.screenshot_requested, ); + + // For Wayland : https://github.com/emilk/egui/issues/4196 + if cfg!(target_os = "linux") { + let new_inner_size = window.inner_size(); + if new_inner_size != old_inner_size { + self.resize(viewport_id, new_inner_size); + } + } } } // Create windows for any new viewports: self.initialize_all_windows(event_loop); - // GC old viewports - self.viewports - .retain(|id, _| active_viewports_ids.contains(id)); - self.viewport_from_window - .retain(|_, id| active_viewports_ids.contains(id)); - self.window_from_viewport - .retain(|id, _| active_viewports_ids.contains(id)); + self.remove_viewports_not_in(viewport_output); } } -fn initialize_or_update_viewport<'vp>( - egu_ctx: &egui::Context, - viewports: &'vp mut ViewportIdMap, +fn initialize_or_update_viewport( + viewports: &mut ViewportIdMap, ids: ViewportIdPair, class: ViewportClass, mut builder: ViewportBuilder, viewport_ui_cb: Option>, - focused_viewport: Option, -) -> &'vp mut Viewport { +) -> &mut Viewport { crate::profile_function!(); if builder.icon.is_none() { @@ -1298,6 +1314,7 @@ fn initialize_or_update_viewport<'vp>( ids, class, builder, + deferred_commands: vec![], info: Default::default(), screenshot_requested: false, viewport_ui_cb, @@ -1315,7 +1332,7 @@ fn initialize_or_update_viewport<'vp>( viewport.class = class; viewport.viewport_ui_cb = viewport_ui_cb; - let (delta_commands, recreate) = viewport.builder.patch(builder); + let (mut delta_commands, recreate) = viewport.builder.patch(builder); if recreate { log::debug!( @@ -1325,18 +1342,10 @@ fn initialize_or_update_viewport<'vp>( ); viewport.window = None; viewport.egui_winit = None; - } else if let Some(window) = &viewport.window { - let is_viewport_focused = focused_viewport == Some(ids.this); - egui_winit::process_viewport_commands( - egu_ctx, - &mut viewport.info, - delta_commands, - window, - is_viewport_focused, - &mut viewport.screenshot_requested, - ); } + viewport.deferred_commands.append(&mut delta_commands); + entry.into_mut() } } @@ -1366,13 +1375,11 @@ fn render_immediate_viewport( let mut glutin = glutin.borrow_mut(); initialize_or_update_viewport( - egui_ctx, &mut glutin.viewports, ids, ViewportClass::Immediate, builder, None, - None, ); if let Err(err) = glutin.initialize_window(viewport_id, event_loop) { @@ -1392,7 +1399,7 @@ fn render_immediate_viewport( let (Some(egui_winit), Some(window)) = (&mut viewport.egui_winit, &viewport.window) else { return; }; - egui_winit::update_viewport_info(&mut viewport.info, egui_ctx, window); + egui_winit::update_viewport_info(&mut viewport.info, egui_ctx, window, false); let mut raw_input = egui_winit.take_egui_input(window); raw_input.viewports = glutin @@ -1480,7 +1487,7 @@ fn render_immediate_viewport( egui_winit.handle_platform_output(window, platform_output); - glutin.handle_viewport_output(event_loop, egui_ctx, viewport_output); + glutin.handle_viewport_output(event_loop, egui_ctx, &viewport_output); } #[cfg(feature = "__screenshot")] diff --git a/crates/eframe/src/native/wgpu_integration.rs b/crates/eframe/src/native/wgpu_integration.rs index dd4debd8b7a..9e0022489e7 100644 --- a/crates/eframe/src/native/wgpu_integration.rs +++ b/crates/eframe/src/native/wgpu_integration.rs @@ -76,6 +76,7 @@ pub struct Viewport { ids: ViewportIdPair, class: ViewportClass, builder: ViewportBuilder, + deferred_commands: Vec, info: ViewportInfo, screenshot_requested: bool, @@ -154,13 +155,11 @@ impl WgpuWinitApp { } = &mut *running.shared.borrow_mut(); initialize_or_update_viewport( - egui_ctx, viewports, ViewportIdPair::ROOT, ViewportClass::Root, self.native_options.viewport.clone(), None, - None, ) .initialize_window(event_loop, egui_ctx, viewport_from_window, painter); } @@ -278,6 +277,9 @@ impl WgpuWinitApp { let mut viewport_from_window = HashMap::default(); viewport_from_window.insert(window.id(), ViewportId::ROOT); + let mut info = ViewportInfo::default(); + egui_winit::update_viewport_info(&mut info, &egui_ctx, &window, true); + let mut viewports = Viewports::default(); viewports.insert( ViewportId::ROOT, @@ -285,11 +287,8 @@ impl WgpuWinitApp { ids: ViewportIdPair::ROOT, class: ViewportClass::Root, builder, - info: ViewportInfo { - minimized: window.is_minimized(), - maximized: Some(window.is_maximized()), - ..Default::default() - }, + deferred_commands: vec![], + info, screenshot_requested: false, viewport_ui_cb: None, window: Some(window), @@ -603,7 +602,7 @@ impl WgpuWinitRunning { let Some(window) = window else { return EventResult::Wait; }; - egui_winit::update_viewport_info(info, &integration.egui_ctx, window); + egui_winit::update_viewport_info(info, &integration.egui_ctx, window, false); { crate::profile_scope!("set_window"); @@ -638,7 +637,7 @@ impl WgpuWinitRunning { // ------------------------------------------------------------ - let mut shared = shared.borrow_mut(); + let mut shared_mut = shared.borrow_mut(); let SharedState { egui_ctx, @@ -646,7 +645,17 @@ impl WgpuWinitRunning { painter, viewport_from_window, focused_viewport, - } = &mut *shared; + } = &mut *shared_mut; + + let FullOutput { + platform_output, + textures_delta, + shapes, + pixels_per_point, + viewport_output, + } = full_output; + + remove_viewports_not_in(viewports, painter, viewport_from_window, &viewport_output); let Some(viewport) = viewports.get_mut(&viewport_id) else { return EventResult::Wait; @@ -663,14 +672,6 @@ impl WgpuWinitRunning { return EventResult::Wait; }; - let FullOutput { - platform_output, - textures_delta, - shapes, - pixels_per_point, - viewport_output, - } = full_output; - egui_winit.handle_platform_output(window, platform_output); let clipped_primitives = egui_ctx.tessellate(shapes, pixels_per_point); @@ -700,8 +701,10 @@ impl WgpuWinitRunning { handle_viewport_output( &integration.egui_ctx, - viewport_output, + &viewport_output, viewports, + painter, + viewport_from_window, *focused_viewport, ); @@ -876,9 +879,7 @@ impl Viewport { painter.max_texture_side(), )); - self.info.minimized = window.is_minimized(); - self.info.maximized = Some(window.is_maximized()); - + egui_winit::update_viewport_info(&mut self.info, egui_ctx, &window, true); self.window = Some(window); } Err(err) => { @@ -933,15 +934,8 @@ fn render_immediate_viewport( .. } = &mut *shared.borrow_mut(); - let viewport = initialize_or_update_viewport( - egui_ctx, - viewports, - ids, - ViewportClass::Immediate, - builder, - None, - None, - ); + let viewport = + initialize_or_update_viewport(viewports, ids, ViewportClass::Immediate, builder, None); if viewport.window.is_none() { viewport.initialize_window(event_loop, egui_ctx, viewport_from_window, painter); } @@ -949,7 +943,7 @@ fn render_immediate_viewport( let (Some(window), Some(egui_winit)) = (&viewport.window, &mut viewport.egui_winit) else { return; }; - egui_winit::update_viewport_info(&mut viewport.info, egui_ctx, window); + egui_winit::update_viewport_info(&mut viewport.info, egui_ctx, window, false); let mut input = egui_winit.take_egui_input(window); input.viewports = viewports @@ -978,13 +972,14 @@ fn render_immediate_viewport( // ------------------------------------------ - let mut shared = shared.borrow_mut(); + let mut shared_mut = shared.borrow_mut(); let SharedState { viewports, painter, + viewport_from_window, focused_viewport, .. - } = &mut *shared; + } = &mut *shared_mut; let Some(viewport) = viewports.get_mut(&ids.this) else { return; @@ -1016,14 +1011,37 @@ fn render_immediate_viewport( egui_winit.handle_platform_output(window, platform_output); - handle_viewport_output(&egui_ctx, viewport_output, viewports, *focused_viewport); + handle_viewport_output( + &egui_ctx, + &viewport_output, + viewports, + painter, + viewport_from_window, + *focused_viewport, + ); +} + +pub(crate) fn remove_viewports_not_in( + viewports: &mut ViewportIdMap, + painter: &mut egui_wgpu::winit::Painter, + viewport_from_window: &mut HashMap, + viewport_output: &ViewportIdMap, +) { + let active_viewports_ids: ViewportIdSet = viewport_output.keys().copied().collect(); + + // Prune dead viewports: + viewports.retain(|id, _| active_viewports_ids.contains(id)); + viewport_from_window.retain(|_, id| active_viewports_ids.contains(id)); + painter.gc_viewports(&active_viewports_ids); } /// Add new viewports, and update existing ones: fn handle_viewport_output( egui_ctx: &egui::Context, - viewport_output: ViewportIdMap, + viewport_output: &ViewportIdMap, viewports: &mut ViewportIdMap, + painter: &mut egui_wgpu::winit::Painter, + viewport_from_window: &mut HashMap, focused_viewport: Option, ) { for ( @@ -1033,46 +1051,56 @@ fn handle_viewport_output( class, builder, viewport_ui_cb, - commands, + mut commands, repaint_delay: _, // ignored - we listened to the repaint callback instead }, - ) in viewport_output + ) in viewport_output.clone() { let ids = ViewportIdPair::from_self_and_parent(viewport_id, parent); - let viewport = initialize_or_update_viewport( - egui_ctx, - viewports, - ids, - class, - builder, - viewport_ui_cb, - focused_viewport, - ); + let viewport = + initialize_or_update_viewport(viewports, ids, class, builder, viewport_ui_cb); if let Some(window) = viewport.window.as_ref() { + let old_inner_size = window.inner_size(); + let is_viewport_focused = focused_viewport == Some(viewport_id); + viewport.deferred_commands.append(&mut commands); + egui_winit::process_viewport_commands( egui_ctx, &mut viewport.info, - commands, + std::mem::take(&mut viewport.deferred_commands), window, is_viewport_focused, &mut viewport.screenshot_requested, ); + + // For Wayland : https://github.com/emilk/egui/issues/4196 + if cfg!(target_os = "linux") { + let new_inner_size = window.inner_size(); + if new_inner_size != old_inner_size { + if let (Some(width), Some(height)) = ( + NonZeroU32::new(new_inner_size.width), + NonZeroU32::new(new_inner_size.height), + ) { + painter.on_window_resized(viewport_id, width, height); + } + } + } } } + + remove_viewports_not_in(viewports, painter, viewport_from_window, viewport_output); } -fn initialize_or_update_viewport<'vp>( - egui_ctx: &egui::Context, - viewports: &'vp mut Viewports, +fn initialize_or_update_viewport( + viewports: &mut Viewports, ids: ViewportIdPair, class: ViewportClass, mut builder: ViewportBuilder, viewport_ui_cb: Option>, - focused_viewport: Option, -) -> &'vp mut Viewport { +) -> &mut Viewport { crate::profile_function!(); if builder.icon.is_none() { @@ -1090,6 +1118,7 @@ fn initialize_or_update_viewport<'vp>( ids, class, builder, + deferred_commands: vec![], info: Default::default(), screenshot_requested: false, viewport_ui_cb, @@ -1106,7 +1135,7 @@ fn initialize_or_update_viewport<'vp>( viewport.ids.parent = ids.parent; viewport.viewport_ui_cb = viewport_ui_cb; - let (delta_commands, recreate) = viewport.builder.patch(builder); + let (mut delta_commands, recreate) = viewport.builder.patch(builder); if recreate { log::debug!( @@ -1116,18 +1145,10 @@ fn initialize_or_update_viewport<'vp>( ); viewport.window = None; viewport.egui_winit = None; - } else if let Some(window) = &viewport.window { - let is_viewport_focused = focused_viewport == Some(ids.this); - egui_winit::process_viewport_commands( - egui_ctx, - &mut viewport.info, - delta_commands, - window, - is_viewport_focused, - &mut viewport.screenshot_requested, - ); } + viewport.deferred_commands.append(&mut delta_commands); + entry.into_mut() } } diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 3d8e9184fdc..5abfa0f6898 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -874,70 +874,62 @@ impl State { } } +pub fn inner_rect_in_points(window: &Window, pixels_per_point: f32) -> Option { + let inner_pos_px = window.inner_position().ok()?; + let inner_pos_px = egui::pos2(inner_pos_px.x as f32, inner_pos_px.y as f32); + + let inner_size_px = window.inner_size(); + let inner_size_px = egui::vec2(inner_size_px.width as f32, inner_size_px.height as f32); + + let inner_rect_px = egui::Rect::from_min_size(inner_pos_px, inner_size_px); + + Some(inner_rect_px / pixels_per_point) +} + +pub fn outer_rect_in_points(window: &Window, pixels_per_point: f32) -> Option { + let outer_pos_px = window.outer_position().ok()?; + let outer_pos_px = egui::pos2(outer_pos_px.x as f32, outer_pos_px.y as f32); + + let outer_size_px = window.outer_size(); + let outer_size_px = egui::vec2(outer_size_px.width as f32, outer_size_px.height as f32); + + let outer_rect_px = egui::Rect::from_min_size(outer_pos_px, outer_size_px); + + Some(outer_rect_px / pixels_per_point) +} + /// Update the given viewport info with the current state of the window. /// /// Call before [`State::take_egui_input`]. +/// +/// If this is called right after window creation, `is_init` should be `true`, otherwise `false`. pub fn update_viewport_info( viewport_info: &mut ViewportInfo, egui_ctx: &egui::Context, window: &Window, + is_init: bool, ) { crate::profile_function!(); let pixels_per_point = pixels_per_point(egui_ctx, window); let has_a_position = match window.is_minimized() { - None | Some(true) => false, - Some(false) => true, - }; - - let inner_pos_px = if has_a_position { - window - .inner_position() - .map(|pos| Pos2::new(pos.x as f32, pos.y as f32)) - .ok() - } else { - None - }; - - let outer_pos_px = if has_a_position { - window - .outer_position() - .map(|pos| Pos2::new(pos.x as f32, pos.y as f32)) - .ok() - } else { - None - }; - - let inner_size_px = if has_a_position { - let size = window.inner_size(); - Some(Vec2::new(size.width as f32, size.height as f32)) - } else { - None - }; - - let outer_size_px = if has_a_position { - let size = window.outer_size(); - Some(Vec2::new(size.width as f32, size.height as f32)) - } else { - None + Some(true) => false, + Some(false) | None => true, }; - let inner_rect_px = if let (Some(pos), Some(size)) = (inner_pos_px, inner_size_px) { - Some(Rect::from_min_size(pos, size)) + let inner_rect = if has_a_position { + inner_rect_in_points(window, pixels_per_point) } else { None }; - let outer_rect_px = if let (Some(pos), Some(size)) = (outer_pos_px, outer_size_px) { - Some(Rect::from_min_size(pos, size)) + let outer_rect = if has_a_position { + outer_rect_in_points(window, pixels_per_point) } else { None }; - let inner_rect = inner_rect_px.map(|r| r / pixels_per_point); - let outer_rect = outer_rect_px.map(|r| r / pixels_per_point); - let monitor_size = { crate::profile_scope!("monitor_size"); if let Some(monitor) = window.current_monitor() { @@ -948,21 +940,23 @@ pub fn update_viewport_info( } }; - viewport_info.focused = Some(window.has_focus()); - viewport_info.fullscreen = Some(window.fullscreen().is_some()); - viewport_info.inner_rect = inner_rect; - viewport_info.monitor_size = monitor_size; + viewport_info.title = Some(window.title()); viewport_info.native_pixels_per_point = Some(window.scale_factor() as f32); + + viewport_info.monitor_size = monitor_size; + viewport_info.inner_rect = inner_rect; viewport_info.outer_rect = outer_rect; - viewport_info.title = Some(window.title()); - if cfg!(target_os = "windows") { - // It's tempting to do this, but it leads to a deadlock on Mac when running + if is_init || !cfg!(target_os = "macos") { + // Asking for minimized/maximized state at runtime leads to a deadlock on Mac when running // `cargo run -p custom_window_frame`. // See https://github.com/emilk/egui/issues/3494 viewport_info.maximized = Some(window.is_maximized()); viewport_info.minimized = Some(window.is_minimized().unwrap_or(false)); } + + viewport_info.fullscreen = Some(window.fullscreen().is_some()); + viewport_info.focused = Some(window.has_focus()); } fn open_url_in_browser(_url: &str) { @@ -1319,11 +1313,27 @@ fn process_viewport_command( ViewportCommand::InnerSize(size) => { let width_px = pixels_per_point * size.x.max(1.0); let height_px = pixels_per_point * size.y.max(1.0); - if window - .request_inner_size(PhysicalSize::new(width_px, height_px)) - .is_some() - { - log::debug!("ViewportCommand::InnerSize ignored by winit"); + let requested_size = PhysicalSize::new(width_px, height_px); + if let Some(_returned_inner_size) = window.request_inner_size(requested_size) { + // On platforms where the size is entirely controlled by the user the + // applied size will be returned immediately, resize event in such case + // may not be generated. + // e.g. Linux + + // On platforms where resizing is disallowed by the windowing system, the current + // inner size is returned immediately, and the user one is ignored. + // e.g. Android, iOS, … + + // However, comparing the results is prone to numerical errors + // because the linux backend converts physical to logical and back again. + // So let's just assume it worked: + + info.inner_rect = inner_rect_in_points(window, pixels_per_point); + info.outer_rect = outer_rect_in_points(window, pixels_per_point); + } else { + // e.g. macOS, Windows + // The request went to the display system, + // and the actual size will be delivered later with the [`WindowEvent::Resized`]. } } ViewportCommand::BeginResize(direction) => { From bb06befef1389eedb1502e416dfd5508db585748 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 31 Mar 2024 20:06:25 +0200 Subject: [PATCH 078/134] Consider all non-interactie widgets under the mouse pointer hovered (#4291) At least all those above any interactive widget. * Closes https://github.com/emilk/egui/issues/4286 I feel there is still more thinking to be done about what is considered `hovered` and how it relates to `contains_pointer`, but this PR at least fixes tooltips for uninteractive widgets --- crates/egui/src/interaction.rs | 43 +++++++++++++++++++++++++--------- crates/egui/src/widget_rect.rs | 5 ++++ 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/crates/egui/src/interaction.rs b/crates/egui/src/interaction.rs index 8ee3f259a43..8a7b2d0948c 100644 --- a/crates/egui/src/interaction.rs +++ b/crates/egui/src/interaction.rs @@ -242,18 +242,39 @@ pub(crate) fn interact( .chain(&long_touched) .copied() .collect() - } else if hits.click.is_some() || hits.drag.is_some() { - // We are hovering over an interactive widget or two. - hits.click.iter().chain(&hits.drag).map(|w| w.id).collect() } else { - // Whatever is topmost is what we are hovering. - // TODO(emilk): consider handle hovering over multiple top-most widgets? - // TODO(emilk): allow hovering close widgets? - hits.contains_pointer - .last() - .map(|w| w.id) - .into_iter() - .collect() + // We may be hovering a an interactive widget or two. + // We must also consider the case where non-interactive widgets + // are _on top_ of an interactive widget. + // For instance: a label in a draggable window. + // In that case we want to hover _both_ widgets, + // otherwise we won't see tooltips for the label. + // + // Because of how `Ui` work, we will often allocate the `Ui` rect + // _after_ adding the children in it (once we know the size it will occopy) + // so we will also have a lot of such `Ui` widgets rects covering almost any widget. + // + // So: we want to hover _all_ widgets above the interactive widget (if any), + // but none below it (an interactive widget stops the hover search). + // + // To know when to stop we need to first know the order of the widgets, + // which luckily we have in the `WidgetRects`. + + let order = |id| widgets.order(id).map(|(_layer, order)| order); // we ignore the layer, since all widgets at this point is in the same layer + + let click_order = hits.click.and_then(|w| order(w.id)).unwrap_or(0); + let drag_order = hits.drag.and_then(|w| order(w.id)).unwrap_or(0); + let top_interactive_order = click_order.max(drag_order); + + let mut hovered: IdSet = hits.click.iter().chain(&hits.drag).map(|w| w.id).collect(); + + for w in &hits.contains_pointer { + if top_interactive_order <= order(w.id).unwrap_or(0) { + hovered.insert(w.id); + } + } + + hovered }; InteractionSnapshot { diff --git a/crates/egui/src/widget_rect.rs b/crates/egui/src/widget_rect.rs index ab95447e1fd..bf393fe6bd6 100644 --- a/crates/egui/src/widget_rect.rs +++ b/crates/egui/src/widget_rect.rs @@ -72,6 +72,11 @@ impl WidgetRects { self.by_id.get(&id).map(|(_, w)| w) } + /// In which layer, and in which order in that layer? + pub fn order(&self, id: Id) -> Option<(LayerId, usize)> { + self.by_id.get(&id).map(|(idx, w)| (w.layer_id, *idx)) + } + #[inline] pub fn contains(&self, id: Id) -> bool { self.by_id.contains_key(&id) From aa2f87e0ff004ec0bec439ea16ca0c9235eff84f Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 31 Mar 2024 20:20:46 +0200 Subject: [PATCH 079/134] Allow zoom/pan a plot as long as it contains the mouse cursor (#4292) This is a fix meant mostly for Rerun, where we sometiems paint a vertical time-line over the plot (which is interactive). Before this PR you couldn't zoom or pan the plot while hovering that line, which was really annoying. --- crates/egui_plot/src/lib.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/crates/egui_plot/src/lib.rs b/crates/egui_plot/src/lib.rs index 6056f46bd4e..bae9038378a 100644 --- a/crates/egui_plot/src/lib.rs +++ b/crates/egui_plot/src/lib.rs @@ -1076,8 +1076,13 @@ impl Plot { } } - let hover_pos = response.hover_pos(); - if let Some(hover_pos) = hover_pos { + // Note: we catch zoom/pan if the response contains the pointer, even if it isn't hovered. + // For instance: The user is painting another interactive widget on top of the plot + // but they still want to be able to pan/zoom the plot. + if let (true, Some(hover_pos)) = ( + response.contains_pointer, + ui.input(|i| i.pointer.hover_pos()), + ) { if allow_zoom.any() { let mut zoom_factor = if data_aspect.is_some() { Vec2::splat(ui.input(|i| i.zoom_delta())) From 95b62ce144a28cbb50f10a83f071cc26c8011e6e Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 31 Mar 2024 20:32:05 +0200 Subject: [PATCH 080/134] Show `WidgetInfo` for each widget if `debug.show_interactive_widgets` This was useful during debugging --- crates/egui/src/context.rs | 48 +++++++++++++++++++++++++++++----- crates/egui/src/response.rs | 8 ++++++ crates/egui/src/widget_rect.rs | 36 ++++++++++++++++++++++--- 3 files changed, 83 insertions(+), 9 deletions(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 74a71da2141..aa9849523be 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -1202,6 +1202,19 @@ impl Context { res } + /// This is called by [`Response::widget_info`], but can also be called directly. + /// + /// With some debug flags it will store the widget info in [`WidgetRects`] for later display. + #[inline] + pub fn register_widget_info(&self, id: Id, make_info: impl Fn() -> crate::WidgetInfo) { + #[cfg(debug_assertions)] + self.write(|ctx| { + if ctx.memory.options.style.debug.show_interactive_widgets { + ctx.viewport().widgets_this_frame.set_info(id, make_info()); + } + }); + } + /// Get a full-screen painter for a new or existing layer pub fn layer_painter(&self, layer_id: LayerId) -> Painter { let screen_rect = self.screen_rect(); @@ -1807,6 +1820,7 @@ impl Context { self.write(|ctx| ctx.end_frame()) } + /// Called at the end of the frame. #[cfg(debug_assertions)] fn debug_painting(&self) { let paint_widget = |widget: &WidgetRect, text: &str, color: Color32| { @@ -1839,8 +1853,8 @@ impl Context { } else if rect.sense.drag { (Color32::from_rgb(0, 0, 0x88), "drag") } else { - continue; - // (Color32::from_rgb(0, 0, 0x88), "hover") + // unreachable since we only show interactive + (Color32::from_rgb(0, 0, 0x88), "hover") }; painter.debug_rect(rect.interact_rect, color, text); } @@ -1860,10 +1874,32 @@ impl Context { hovered, } = interact_widgets; - if false { - for widget in contains_pointer { - paint_widget_id(widget, "contains_pointer", Color32::BLUE); + if true { + for &id in &contains_pointer { + paint_widget_id(id, "contains_pointer", Color32::BLUE); + } + + let widget_rects = self.write(|w| w.viewport().widgets_this_frame.clone()); + + let mut contains_pointer: Vec = contains_pointer.iter().copied().collect(); + contains_pointer.sort_by_key(|&id| { + widget_rects + .order(id) + .map(|(layer_id, order_in_layer)| (layer_id.order, order_in_layer)) + }); + + let mut debug_text = "Widgets in order:\n".to_owned(); + for id in contains_pointer { + let mut widget_text = format!("{id:?}"); + if let Some(rect) = widget_rects.get(id) { + widget_text += &format!(" {:?} {:?}", rect.rect, rect.sense); + } + if let Some(info) = widget_rects.info(id) { + widget_text += &format!(" {info:?}"); + } + debug_text += &format!("{widget_text}\n"); } + self.debug_text(debug_text); } if true { for widget in hovered { @@ -1887,7 +1923,7 @@ impl Context { drag, } = hits; - if false { + if true { for widget in &contains_pointer { paint_widget(widget, "contains_pointer", Color32::BLUE); } diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index 6946e157f5a..18598f81a66 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -745,6 +745,7 @@ impl Response { /// Call after interacting and potential calls to [`Self::mark_changed`]. pub fn widget_info(&self, make_info: impl Fn() -> crate::WidgetInfo) { use crate::output::OutputEvent; + let event = if self.clicked() { Some(OutputEvent::Clicked(make_info())) } else if self.double_clicked() { @@ -758,6 +759,7 @@ impl Response { } else { None }; + if let Some(event) = event { self.output_event(event); } else { @@ -765,6 +767,8 @@ impl Response { self.ctx.accesskit_node_builder(self.id, |builder| { self.fill_accesskit_node_from_widget_info(builder, make_info()); }); + + self.ctx.register_widget_info(self.id, make_info); } } @@ -773,6 +777,10 @@ impl Response { self.ctx.accesskit_node_builder(self.id, |builder| { self.fill_accesskit_node_from_widget_info(builder, event.widget_info().clone()); }); + + self.ctx + .register_widget_info(self.id, || event.widget_info().clone()); + self.ctx.output_mut(|o| o.events.push(event)); } diff --git a/crates/egui/src/widget_rect.rs b/crates/egui/src/widget_rect.rs index bf393fe6bd6..7c502f13927 100644 --- a/crates/egui/src/widget_rect.rs +++ b/crates/egui/src/widget_rect.rs @@ -46,13 +46,25 @@ pub struct WidgetRect { /// /// All [`Ui`]s have a [`WidgetRects`], but whether or not their rects are correct /// depends on if [`Ui::interact_bg`] was ever called. -#[derive(Default, Clone, PartialEq, Eq)] +#[derive(Default, Clone)] pub struct WidgetRects { /// All widgets, in painting order. by_layer: HashMap>, /// All widgets, by id, and their order in their respective layer by_id: IdMap<(usize, WidgetRect)>, + + /// Info about some widgets. + /// + /// Only filled in if the widget is interacted with, + /// or if this is a debug build. + infos: IdMap, +} + +impl PartialEq for WidgetRects { + fn eq(&self, other: &Self) -> bool { + self.by_layer == other.by_layer + } } impl WidgetRects { @@ -90,18 +102,28 @@ impl WidgetRects { /// Clear the contents while retaining allocated memory. pub fn clear(&mut self) { - let Self { by_layer, by_id } = self; + let Self { + by_layer, + by_id, + infos, + } = self; for rects in by_layer.values_mut() { rects.clear(); } by_id.clear(); + + infos.clear(); } /// Insert the given widget rect in the given layer. pub fn insert(&mut self, layer_id: LayerId, widget_rect: WidgetRect) { - let Self { by_layer, by_id } = self; + let Self { + by_layer, + by_id, + infos: _, + } = self; let layer_widgets = by_layer.entry(layer_id).or_default(); @@ -134,4 +156,12 @@ impl WidgetRects { } } } + + pub fn set_info(&mut self, id: Id, info: WidgetInfo) { + self.infos.insert(id, info); + } + + pub fn info(&self, id: Id) -> Option<&WidgetInfo> { + self.infos.get(&id) + } } From a97134d66c30f876118e9eefef7686ee8645af27 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 31 Mar 2024 20:33:02 +0200 Subject: [PATCH 081/134] Improve `Debug` format of `Sense`, `WidgetInfo` and `Id` --- crates/egui/src/data/output.rs | 5 ++++- crates/egui/src/id.rs | 2 +- crates/egui/src/sense.rs | 24 +++++++++++++++++++++++- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/crates/egui/src/data/output.rs b/crates/egui/src/data/output.rs index d1db5fc2a41..97794f4d13e 100644 --- a/crates/egui/src/data/output.rs +++ b/crates/egui/src/data/output.rs @@ -504,7 +504,10 @@ impl std::fmt::Debug for WidgetInfo { let mut s = f.debug_struct("WidgetInfo"); s.field("typ", typ); - s.field("enabled", enabled); + + if !enabled { + s.field("enabled", enabled); + } if let Some(label) = label { s.field("label", label); diff --git a/crates/egui/src/id.rs b/crates/egui/src/id.rs index 1cd96faec5f..0465c32d76c 100644 --- a/crates/egui/src/id.rs +++ b/crates/egui/src/id.rs @@ -85,7 +85,7 @@ impl Id { impl std::fmt::Debug for Id { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{:016X}", self.0) + write!(f, "{:04X}", self.value() as u16) } } diff --git a/crates/egui/src/sense.rs b/crates/egui/src/sense.rs index ca216896aee..1b6394b2cc3 100644 --- a/crates/egui/src/sense.rs +++ b/crates/egui/src/sense.rs @@ -1,5 +1,5 @@ /// What sort of interaction is a widget sensitive to? -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Eq, PartialEq)] // #[cfg_attr(feature = "serde", derive(serde::Serialize))] pub struct Sense { /// Buttons, sliders, windows, … @@ -15,6 +15,28 @@ pub struct Sense { pub focusable: bool, } +impl std::fmt::Debug for Sense { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let Self { + click, + drag, + focusable, + } = self; + + write!(f, "Sense {{")?; + if *click { + write!(f, " click")?; + } + if *drag { + write!(f, " drag")?; + } + if *focusable { + write!(f, " focusable")?; + } + write!(f, " }}") + } +} + impl Sense { /// Senses no clicks or drags. Only senses mouse hover. #[doc(alias = "none")] From 3ee4890b9433e8038de4cb498a3a9d09eb0d0c4b Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 31 Mar 2024 20:41:44 +0200 Subject: [PATCH 082/134] Remove warning in release build --- crates/egui/src/context.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index aa9849523be..1ee0b364136 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -1213,6 +1213,11 @@ impl Context { ctx.viewport().widgets_this_frame.set_info(id, make_info()); } }); + + #[cfg(not(debug_assertions))] + { + _ = (self, id, make_info); + } } /// Get a full-screen painter for a new or existing layer From e99bd00dec0b40ee660149459996f2ec15cc2a7a Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 1 Apr 2024 12:14:44 +0200 Subject: [PATCH 083/134] Only avoid glow context switching on Windows (#4296) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …since it is reportedly broken on Windows * Early-out added in https://github.com/emilk/egui/pull/4284 * Re-opens https://github.com/emilk/egui/issues/4173 😭 * Closes https://github.com/emilk/egui/issues/4289 * Closes https://github.com/emilk/egui/pull/4290 --- crates/eframe/src/native/glow_integration.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index 823e0b0201a..84b3fd94528 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -849,10 +849,17 @@ fn change_gl_context( ) { crate::profile_function!(); - if let Some(current_gl_context) = current_gl_context { - crate::profile_scope!("is_current"); - if gl_surface.is_current(current_gl_context) { - return; // Early-out to save a lot of time. + if !cfg!(target_os = "windows") { + // According to https://github.com/emilk/egui/issues/4289 + // we cannot do this early-out on Windows. + // TODO(emilk): optimize context switching on Windows too. + // See https://github.com/emilk/egui/issues/4173 + + if let Some(current_gl_context) = current_gl_context { + crate::profile_scope!("is_current"); + if gl_surface.is_current(current_gl_context) { + return; // Early-out to save a lot of time. + } } } From 48ecf01e11e9bacb798dc03c7fc43927c276340b Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 1 Apr 2024 13:08:52 +0200 Subject: [PATCH 084/134] Rename "Color test" to "Rendering test", and restructure it slightly (#4298) Put the pixel-alignment test first, and hide the color test under a collapsing header. Screenshot 2024-04-01 at 13 01 43 --- crates/egui_demo_app/src/wrap_app.rs | 19 +++-- crates/egui_demo_lib/src/lib.rs | 4 +- .../src/{color_test.rs => rendering_test.rs} | 69 ++++++++++++------- 3 files changed, 60 insertions(+), 32 deletions(-) rename crates/egui_demo_lib/src/{color_test.rs => rendering_test.rs} (93%) diff --git a/crates/egui_demo_app/src/wrap_app.rs b/crates/egui_demo_app/src/wrap_app.rs index db3ee385d9b..98e74d0deea 100644 --- a/crates/egui_demo_app/src/wrap_app.rs +++ b/crates/egui_demo_app/src/wrap_app.rs @@ -79,15 +79,22 @@ impl eframe::App for ColorTestApp { #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] enum Anchor { Demo, + EasyMarkEditor, + #[cfg(feature = "http")] Http, + #[cfg(feature = "image_viewer")] ImageViewer, + Clock, + #[cfg(any(feature = "glow", feature = "wgpu"))] Custom3d, - Colors, + + /// Rendering test + Rendering, } impl Anchor { @@ -101,7 +108,7 @@ impl Anchor { Self::Clock, #[cfg(any(feature = "glow", feature = "wgpu"))] Self::Custom3d, - Self::Colors, + Self::Rendering, ] } } @@ -147,7 +154,7 @@ pub struct State { #[cfg(feature = "image_viewer")] image_viewer: crate::apps::ImageViewer, clock: FractalClockApp, - color_test: ColorTestApp, + rendering_test: ColorTestApp, selected_anchor: Anchor, backend_panel: super::backend_panel::BackendPanel, @@ -229,9 +236,9 @@ impl WrapApp { } vec.push(( - "🎨 Color test", - Anchor::Colors, - &mut self.state.color_test as &mut dyn eframe::App, + "🎨 Rendering test", + Anchor::Rendering, + &mut self.state.rendering_test as &mut dyn eframe::App, )); vec.into_iter() diff --git a/crates/egui_demo_lib/src/lib.rs b/crates/egui_demo_lib/src/lib.rs index cf31ee8d43b..7dba93b4117 100644 --- a/crates/egui_demo_lib/src/lib.rs +++ b/crates/egui_demo_lib/src/lib.rs @@ -13,12 +13,12 @@ #![cfg_attr(feature = "puffin", deny(unsafe_code))] #![cfg_attr(not(feature = "puffin"), forbid(unsafe_code))] -mod color_test; mod demo; pub mod easy_mark; +mod rendering_test; -pub use color_test::ColorTest; pub use demo::{DemoWindows, WidgetGallery}; +pub use rendering_test::ColorTest; /// View some Rust code with syntax highlighting and selection. pub(crate) fn rust_view_ui(ui: &mut egui::Ui, code: &str) { diff --git a/crates/egui_demo_lib/src/color_test.rs b/crates/egui_demo_lib/src/rendering_test.rs similarity index 93% rename from crates/egui_demo_lib/src/color_test.rs rename to crates/egui_demo_lib/src/rendering_test.rs index ebbb3076ffc..2aa6194eea1 100644 --- a/crates/egui_demo_lib/src/color_test.rs +++ b/crates/egui_demo_lib/src/rendering_test.rs @@ -31,17 +31,43 @@ impl Default for ColorTest { impl ColorTest { pub fn ui(&mut self, ui: &mut Ui) { - ui.set_max_width(680.0); - ui.vertical_centered(|ui| { ui.add(crate::egui_github_link_file!()); }); ui.horizontal_wrapped(|ui|{ - ui.label("This is made to test that the egui painter backend is set up correctly."); + ui.label("This is made to test that the egui rendering backend is set up correctly."); ui.add(egui::Label::new("❓").sense(egui::Sense::click())) .on_hover_text("The texture sampling should be sRGB-aware, and every other color operation should be done in gamma-space (sRGB). All colors should use pre-multiplied alpha"); }); + + ui.separator(); + + pixel_test(ui); + + ui.separator(); + + ui.collapsing("Color test", |ui| { + self.color_test(ui); + }); + + ui.separator(); + + ui.heading("Text rendering"); + + text_on_bg(ui, Color32::from_gray(200), Color32::from_gray(230)); // gray on gray + text_on_bg(ui, Color32::from_gray(140), Color32::from_gray(28)); // dark mode normal text + + // Matches Mac Font book (useful for testing): + text_on_bg(ui, Color32::from_gray(39), Color32::from_gray(255)); + text_on_bg(ui, Color32::from_gray(220), Color32::from_gray(30)); + + ui.separator(); + + blending_and_feathering_test(ui); + } + + fn color_test(&mut self, ui: &mut Ui) { ui.label("If the rendering is done right, all groups of gradients will look uniform."); ui.horizontal(|ui| { @@ -134,24 +160,6 @@ impl ColorTest { ui.label("Linear interpolation (texture sampling):"); self.show_gradients(ui, WHITE, (RED, GREEN), Interpolation::Linear); - - ui.separator(); - - pixel_test(ui); - - ui.separator(); - ui.label("Testing text rendering:"); - - text_on_bg(ui, Color32::from_gray(200), Color32::from_gray(230)); // gray on gray - text_on_bg(ui, Color32::from_gray(140), Color32::from_gray(28)); // dark mode normal text - - // Matches Mac Font book (useful for testing): - text_on_bg(ui, Color32::from_gray(39), Color32::from_gray(255)); - text_on_bg(ui, Color32::from_gray(220), Color32::from_gray(30)); - - ui.separator(); - - blending_and_feathering_test(ui); } fn show_gradients( @@ -385,8 +393,17 @@ impl TextureManager { } } -fn pixel_test(ui: &mut Ui) { - ui.label("Each subsequent square should be one physical pixel larger than the previous. They should be exactly one physical pixel apart. They should be perfectly aligned to the pixel grid."); +/// A visual test that the rendering is correctly aligned on the physical pixel grid. +/// +/// Requires eyes and a magnifying glass to verify. +pub fn pixel_test(ui: &mut Ui) { + ui.heading("Pixel alignment test"); + ui.label("The first square should be exactly one physical pixel big."); + ui.label("They should be exactly one physical pixel apart."); + ui.label("Each subsequent square should be one physical pixel larger than the previous."); + ui.label("They should be perfectly aligned to the physical pixel grid."); + ui.label("If these squares are blurry, everything will be blurry, including text."); + ui.label("You might need a magnifying glass to check this test."); let color = if ui.style().visuals.dark_mode { egui::Color32::WHITE @@ -395,7 +412,7 @@ fn pixel_test(ui: &mut Ui) { }; let pixels_per_point = ui.ctx().pixels_per_point(); - let num_squares: u32 = 8; + let num_squares = (pixels_per_point * 10.0).round().max(10.0) as u32; let size_pixels = vec2( ((num_squares + 1) * (num_squares + 2) / 2) as f32, num_squares as f32, @@ -422,6 +439,10 @@ fn pixel_test(ui: &mut Ui) { } fn blending_and_feathering_test(ui: &mut Ui) { + ui.label("The left side shows how lines of different widths look."); + ui.label("The right side tests text rendering at different opacities and sizes."); + ui.label("The top and bottom images should look symmetrical in their intensities."); + let size = vec2(512.0, 512.0); let (response, painter) = ui.allocate_painter(size, Sense::hover()); let rect = response.rect; From 0a40b16bd410aa9ac882c4c2ffe2cb50acdcd0ff Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 1 Apr 2024 15:22:47 +0200 Subject: [PATCH 085/134] Fix blurry rendering in some browsers (#4299) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Closes https://github.com/emilk/egui/issues/4241 I would love some more testers of this. I'm not sure if we really need the round-to-even code, but I'm hesitant to out-right revert https://github.com/emilk/egui/pull/151 when I cannot reproduce its problem. Keeping it seems quite safe though. --- # Testing Checkout the branch and run: * `./scripts/start_server.sh` * `./scripts/build_demo_web.sh` and then open `http://localhost:8888/index.html#Rendering` * `./scripts/build_demo_web.sh --wgpu` and then open `http://localhost:8888/index.html#Rendering` Check the "Rendering test" that the squares in the pixel alignment test are perfectly sharp, like this: Screenshot 2024-04-01 at 13 27 20 If it looks something like this, something is WRONG: Screenshot 2024-04-01 at 13 29 07 Please try it on different zoom levels in different browsers, and if possible on different monitors with different native dpi scaling. Report back the results! ### Mac I have tested on a high-DPI Mac: * Chromium (Brave): ✅ Can reproduce problem on `master`, and it's now fixed * Firefox: ✅ Can reproduce problem on `master`, and it's now fixed * Safari: ❌ Can't get it to work; giving up for now --- crates/eframe/src/web/mod.rs | 51 ++++++++++++++++-------------------- web_demo/index.html | 7 ++++- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/crates/eframe/src/web/mod.rs b/crates/eframe/src/web/mod.rs index a322a530aa6..566c9b03c8d 100644 --- a/crates/eframe/src/web/mod.rs +++ b/crates/eframe/src/web/mod.rs @@ -130,51 +130,46 @@ fn resize_canvas_to_screen_size( ) -> Option<()> { let parent = canvas.parent_element()?; + // In this function we use "pixel" to mean physical pixel, + // and "point" to mean "logical CSS pixel". + let pixels_per_point = native_pixels_per_point(); + // Prefer the client width and height so that if the parent // element is resized that the egui canvas resizes appropriately. - let width = parent.client_width(); - let height = parent.client_height(); - - let canvas_real_size = Vec2 { - x: width as f32, - y: height as f32, + let parent_size_points = Vec2 { + x: parent.client_width() as f32, + y: parent.client_height() as f32, }; - if width <= 0 || height <= 0 { - log::error!("egui canvas parent size is {}x{}. Try adding `html, body {{ height: 100%; width: 100% }}` to your CSS!", width, height); + if parent_size_points.x <= 0.0 || parent_size_points.y <= 0.0 { + log::error!("The parent element of the egui canvas is {}x{}. Try adding `html, body {{ height: 100%; width: 100% }}` to your CSS!", parent_size_points.x, parent_size_points.y); } - let pixels_per_point = native_pixels_per_point(); + // We take great care here to ensure the rendered canvas aligns + // perfectly to the physical pixel grid, lest we get blurry text. + // At the time of writing, we get pixel perfection on Chromium and Firefox on Mac, + // but Desktop Safari will be blurry on most zoom levels. + // See https://github.com/emilk/egui/issues/4241 for more. - let max_size_pixels = pixels_per_point * max_size_points; + let canvas_size_pixels = pixels_per_point * parent_size_points.min(max_size_points); - let canvas_size_pixels = pixels_per_point * canvas_real_size; - let canvas_size_pixels = canvas_size_pixels.min(max_size_pixels); - let canvas_size_points = canvas_size_pixels / pixels_per_point; - - // Make sure that the height and width are always even numbers. + // Make sure that the size is always an even number of pixels, // otherwise, the page renders blurry on some platforms. // See https://github.com/emilk/egui/issues/103 - fn round_to_even(v: f32) -> f32 { - (v / 2.0).round() * 2.0 - } + let canvas_size_pixels = (canvas_size_pixels / 2.0).round() * 2.0; + + let canvas_size_points = canvas_size_pixels / pixels_per_point; canvas .style() - .set_property( - "width", - &format!("{}px", round_to_even(canvas_size_points.x)), - ) + .set_property("width", &format!("{}px", canvas_size_points.x)) .ok()?; canvas .style() - .set_property( - "height", - &format!("{}px", round_to_even(canvas_size_points.y)), - ) + .set_property("height", &format!("{}px", canvas_size_points.y)) .ok()?; - canvas.set_width(round_to_even(canvas_size_pixels.x) as u32); - canvas.set_height(round_to_even(canvas_size_pixels.y) as u32); + canvas.set_width(canvas_size_pixels.x as u32); + canvas.set_height(canvas_size_pixels.y as u32); Some(()) } diff --git a/web_demo/index.html b/web_demo/index.html index afe4cc646e1..142355b48f8 100644 --- a/web_demo/index.html +++ b/web_demo/index.html @@ -37,7 +37,12 @@ width: 100%; } - /* Position canvas in center-top: */ + /* Position canvas in center-top. + This is rather arbitrarily chosen. + In particular, it seems like both Chromium and Firefox will still align + the canvas on the physical pixel grid, which is required to get + pixel-perfect (non-blurry) rendering in egui. + See https://github.com/emilk/egui/issues/4241 for more */ canvas { margin-right: auto; margin-left: auto; From 058f4753b0cec2f86fd800d6ca5544206bf694ec Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 2 Apr 2024 09:55:13 +0200 Subject: [PATCH 086/134] Fix typos and false positives found by new version of 'typos' --- .typos.toml | 2 ++ crates/eframe/src/native/glow_integration.rs | 4 ++-- crates/egui/src/context.rs | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.typos.toml b/.typos.toml index 6d856495178..de51a691cc0 100644 --- a/.typos.toml +++ b/.typos.toml @@ -3,7 +3,9 @@ # run: typos [default.extend-words] +ime = "ime" # Input Method Editor nknown = "nknown" # part of @55nknown username +ro = "ro" # read-only, also part of the username @Phen-Ro [files] extend-exclude = ["web_demo/egui_demo_app.js"] # auto-generated diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index 84b3fd94528..7c9eada1ccc 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -714,7 +714,7 @@ impl GlowWinitRunning { #[cfg(feature = "__screenshot")] if integration.egui_ctx.frame_nr() == 2 { if let Ok(path) = std::env::var("EFRAME_SCREENSHOT_TO") { - save_screeshot_and_exit(&path, &painter, screen_size_in_pixels); + save_screenshot_and_exit(&path, &painter, screen_size_in_pixels); } } @@ -1498,7 +1498,7 @@ fn render_immediate_viewport( } #[cfg(feature = "__screenshot")] -fn save_screeshot_and_exit( +fn save_screenshot_and_exit( path: &str, painter: &egui_glow::Painter, screen_size_in_pixels: [u32; 2], diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 1ee0b364136..aa8a0b13e32 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -2818,7 +2818,7 @@ impl Context { /// The `Context` lock is held while the given closure is called! /// /// Returns `None` if acesskit is off. - // TODO(emilk): consider making both RO and RW versions + // TODO(emilk): consider making both read-only and read-write versions #[cfg(feature = "accesskit")] pub fn accesskit_node_builder( &self, From 4bc7e6624507347fff453972f171ff4ec049f215 Mon Sep 17 00:00:00 2001 From: Alexander Parlett Date: Tue, 2 Apr 2024 09:33:14 +0100 Subject: [PATCH 087/134] Support order on windows (#4301) Adds support for the ordering of windows passing through to the underlying area. Needed for a specific usecase of adding debug tools using windows with the bevy integration. --- crates/egui/src/containers/window.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index 2b266dbd8ff..0f336743bd8 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -104,6 +104,13 @@ impl<'open> Window<'open> { self } + /// `order(Order::Foreground)` for a Window that should always be on top + #[inline] + pub fn order(mut self, order: Order) -> Self { + self.area = self.area.order(order); + self + } + /// Usage: `Window::new(…).mutate(|w| w.resize = w.resize.auto_expand_width(true))` // TODO(emilk): I'm not sure this is a good interface for this. #[inline] From 36ebce163a74af40f6216cba2d68aa4c728443eb Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 2 Apr 2024 15:37:44 +0200 Subject: [PATCH 088/134] egui_plots: Fix the same plot tick label being painted multiple times (#4307) Usually this isn't visible (the same label being painted on top of itself), but it will be visible if the user has a custom formatter (e.g. `y_axis_formatter`) that choses a different format based on `GridMark:step_size` (e.g. using fewer decimals for thicker ticks). --- crates/egui_plot/src/lib.rs | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/crates/egui_plot/src/lib.rs b/crates/egui_plot/src/lib.rs index bae9038378a..a5adf1c11c1 100644 --- a/crates/egui_plot/src/lib.rs +++ b/crates/egui_plot/src/lib.rs @@ -13,7 +13,7 @@ mod memory; mod plot_ui; mod transform; -use std::{ops::RangeInclusive, sync::Arc}; +use std::{cmp::Ordering, ops::RangeInclusive, sync::Arc}; use egui::ahash::HashMap; use egui::*; @@ -1717,9 +1717,31 @@ fn generate_marks(step_sizes: [f64; 3], bounds: (f64, f64)) -> Vec { fill_marks_between(&mut steps, step_sizes[0], bounds); fill_marks_between(&mut steps, step_sizes[1], bounds); fill_marks_between(&mut steps, step_sizes[2], bounds); + + // Remove duplicates: + // This can happen because we have overlapping steps, e.g.: + // step_size[0] = 10 => [-10, 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120] + // step_size[1] = 100 => [ 0, 100 ] + // step_size[2] = 1000 => [ 0 ] + + steps.sort_by(|a, b| match cmp_f64(a.value, b.value) { + // Keep the largest step size when we dedup later + Ordering::Equal => cmp_f64(b.step_size, a.step_size), + + ord => ord, + }); + steps.dedup_by(|a, b| a.value == b.value); + steps } +fn cmp_f64(a: f64, b: f64) -> Ordering { + match a.partial_cmp(&b) { + Some(ord) => ord, + None => a.is_nan().cmp(&b.is_nan()), + } +} + /// Fill in all values between [min, max] which are a multiple of `step_size` fn fill_marks_between(out: &mut Vec, step_size: f64, (min, max): (f64, f64)) { debug_assert!(max > min); From de9e0adf17601d31cde3a74a7e7b4ca7ec03d166 Mon Sep 17 00:00:00 2001 From: lucasmerlin Date: Tue, 2 Apr 2024 17:53:54 +0200 Subject: [PATCH 089/134] Allow disabling animations on a ScrollArea (#4309) Adds a flag to ScrollArea that disables animations for the scroll_to_* functions --- crates/egui/src/containers/scroll_area.rs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index 9c05068f60f..c448269fb0c 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -176,6 +176,9 @@ pub struct ScrollArea { /// end position until user manually changes position. It will become true /// again once scroll handle makes contact with end. stick_to_end: Vec2b, + + /// If false, `scroll_to_*` functions will not be animated + animated: bool, } impl ScrollArea { @@ -219,6 +222,7 @@ impl ScrollArea { scrolling_enabled: true, drag_to_scroll: true, stick_to_end: Vec2b::FALSE, + animated: true, } } @@ -393,6 +397,15 @@ impl ScrollArea { self } + /// Should the scroll area animate `scroll_to_*` functions? + /// + /// Default: `true`. + #[inline] + pub fn animated(mut self, animated: bool) -> Self { + self.animated = animated; + self + } + /// Is any scrolling enabled? pub(crate) fn is_any_scroll_enabled(&self) -> bool { self.scroll_enabled[0] || self.scroll_enabled[1] @@ -459,6 +472,7 @@ struct Prepared { scrolling_enabled: bool, stick_to_end: Vec2b, + animated: bool, } impl ScrollArea { @@ -475,6 +489,7 @@ impl ScrollArea { scrolling_enabled, drag_to_scroll, stick_to_end, + animated, } = self; let ctx = ui.ctx().clone(); @@ -650,6 +665,7 @@ impl ScrollArea { viewport, scrolling_enabled, stick_to_end, + animated, } } @@ -761,6 +777,7 @@ impl Prepared { viewport: _, scrolling_enabled, stick_to_end, + animated, } = self; let content_size = content_ui.min_size(); @@ -804,7 +821,9 @@ impl Prepared { if delta != 0.0 { let target_offset = state.offset[d] + delta; - if let Some(animation) = &mut state.offset_target[d] { + if !animated { + state.offset[d] = target_offset; + } else if let Some(animation) = &mut state.offset_target[d] { // For instance: the user is continuously calling `ui.scroll_to_cursor`, // so we don't want to reset the animation, but perhaps update the target: animation.target_offset = target_offset; From 15b0ef3259e208e7d0b7ec24dfb9eb5da58de77f Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 2 Apr 2024 18:13:37 +0200 Subject: [PATCH 090/134] Release 0.27.2 - Fix blurry web rendering --- CHANGELOG.md | 11 +++++++++++ Cargo.lock | 24 ++++++++++++------------ Cargo.toml | 24 ++++++++++++------------ crates/ecolor/CHANGELOG.md | 4 ++++ crates/eframe/CHANGELOG.md | 10 ++++++++++ crates/egui-wgpu/CHANGELOG.md | 4 ++++ crates/egui-winit/CHANGELOG.md | 4 ++++ crates/egui_extras/CHANGELOG.md | 4 ++++ crates/egui_glow/CHANGELOG.md | 4 ++++ crates/egui_plot/CHANGELOG.md | 6 ++++++ crates/epaint/CHANGELOG.md | 4 ++++ 11 files changed, 75 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 764b01eeeb8..35cab321fcd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,17 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.27.2 - 2024-04-02 +### 🐛 Fixed +* Fix tooltips for non-interactive widgets [#4291](https://github.com/emilk/egui/pull/4291) +* Fix problem clicking the edge of a `TextEdit` [#4272](https://github.com/emilk/egui/pull/4272) +* Fix: `Response::clicked_elsewhere` takes clip rect into account [#4274](https://github.com/emilk/egui/pull/4274) +* Fix incorrect `Response::interact_rect` for `Area/Window` [#4273](https://github.com/emilk/egui/pull/4273) + +### ⭐ Added +* Allow disabling animations on a `ScrollArea` [#4309](https://github.com/emilk/egui/pull/4309) (thanks [@lucasmerlin](https://github.com/lucasmerlin)!) + + ## 0.27.1 - 2024-03-29 ### 🐛 Fixed * Fix visual glitch on the right side of highly rounded rectangles [#4244](https://github.com/emilk/egui/pull/4244) diff --git a/Cargo.lock b/Cargo.lock index 0a72704b809..c6d8727188a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1187,7 +1187,7 @@ checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" [[package]] name = "ecolor" -version = "0.27.1" +version = "0.27.2" dependencies = [ "bytemuck", "cint", @@ -1198,7 +1198,7 @@ dependencies = [ [[package]] name = "eframe" -version = "0.27.1" +version = "0.27.2" dependencies = [ "bytemuck", "cocoa", @@ -1236,7 +1236,7 @@ dependencies = [ [[package]] name = "egui" -version = "0.27.1" +version = "0.27.2" dependencies = [ "accesskit", "ahash", @@ -1252,7 +1252,7 @@ dependencies = [ [[package]] name = "egui-wgpu" -version = "0.27.1" +version = "0.27.2" dependencies = [ "bytemuck", "document-features", @@ -1269,7 +1269,7 @@ dependencies = [ [[package]] name = "egui-winit" -version = "0.27.1" +version = "0.27.2" dependencies = [ "accesskit_winit", "arboard", @@ -1287,7 +1287,7 @@ dependencies = [ [[package]] name = "egui_demo_app" -version = "0.27.1" +version = "0.27.2" dependencies = [ "bytemuck", "chrono", @@ -1312,7 +1312,7 @@ dependencies = [ [[package]] name = "egui_demo_lib" -version = "0.27.1" +version = "0.27.2" dependencies = [ "chrono", "criterion", @@ -1327,7 +1327,7 @@ dependencies = [ [[package]] name = "egui_extras" -version = "0.27.1" +version = "0.27.2" dependencies = [ "chrono", "document-features", @@ -1345,7 +1345,7 @@ dependencies = [ [[package]] name = "egui_glow" -version = "0.27.1" +version = "0.27.2" dependencies = [ "bytemuck", "document-features", @@ -1365,7 +1365,7 @@ dependencies = [ [[package]] name = "egui_plot" -version = "0.27.1" +version = "0.27.2" dependencies = [ "document-features", "egui", @@ -1394,7 +1394,7 @@ checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "emath" -version = "0.27.1" +version = "0.27.2" dependencies = [ "bytemuck", "document-features", @@ -1469,7 +1469,7 @@ dependencies = [ [[package]] name = "epaint" -version = "0.27.1" +version = "0.27.2" dependencies = [ "ab_glyph", "ahash", diff --git a/Cargo.toml b/Cargo.toml index bde03d7c8fb..598eae08cb1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ members = [ edition = "2021" license = "MIT OR Apache-2.0" rust-version = "1.72" -version = "0.27.1" +version = "0.27.2" [profile.release] @@ -48,17 +48,17 @@ opt-level = 2 [workspace.dependencies] -emath = { version = "0.27.1", path = "crates/emath", default-features = false } -ecolor = { version = "0.27.1", path = "crates/ecolor", default-features = false } -epaint = { version = "0.27.1", path = "crates/epaint", default-features = false } -egui = { version = "0.27.1", path = "crates/egui", default-features = false } -egui_plot = { version = "0.27.1", path = "crates/egui_plot", default-features = false } -egui-winit = { version = "0.27.1", path = "crates/egui-winit", default-features = false } -egui_extras = { version = "0.27.1", path = "crates/egui_extras", default-features = false } -egui-wgpu = { version = "0.27.1", path = "crates/egui-wgpu", default-features = false } -egui_demo_lib = { version = "0.27.1", path = "crates/egui_demo_lib", default-features = false } -egui_glow = { version = "0.27.1", path = "crates/egui_glow", default-features = false } -eframe = { version = "0.27.1", path = "crates/eframe", default-features = false } +emath = { version = "0.27.2", path = "crates/emath", default-features = false } +ecolor = { version = "0.27.2", path = "crates/ecolor", default-features = false } +epaint = { version = "0.27.2", path = "crates/epaint", default-features = false } +egui = { version = "0.27.2", path = "crates/egui", default-features = false } +egui_plot = { version = "0.27.2", path = "crates/egui_plot", default-features = false } +egui-winit = { version = "0.27.2", path = "crates/egui-winit", default-features = false } +egui_extras = { version = "0.27.2", path = "crates/egui_extras", default-features = false } +egui-wgpu = { version = "0.27.2", path = "crates/egui-wgpu", default-features = false } +egui_demo_lib = { version = "0.27.2", path = "crates/egui_demo_lib", default-features = false } +egui_glow = { version = "0.27.2", path = "crates/egui_glow", default-features = false } +eframe = { version = "0.27.2", path = "crates/eframe", default-features = false } #TODO(emilk): make more things workspace dependencies ahash = { version = "0.8.6", default-features = false, features = [ diff --git a/crates/ecolor/CHANGELOG.md b/crates/ecolor/CHANGELOG.md index 493d125e7f1..3316a408944 100644 --- a/crates/ecolor/CHANGELOG.md +++ b/crates/ecolor/CHANGELOG.md @@ -6,6 +6,10 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.27.2 - 2024-04-02 +* Nothing new + + ## 0.27.1 - 2024-03-29 * Nothing new diff --git a/crates/eframe/CHANGELOG.md b/crates/eframe/CHANGELOG.md index e110500cb53..dfea5671a5d 100644 --- a/crates/eframe/CHANGELOG.md +++ b/crates/eframe/CHANGELOG.md @@ -7,6 +7,16 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.27.2 - 2024-04-02 +#### Desktop/Native +* Fix continuous repaint on Wayland when TextEdit is focused or IME output is set [#4269](https://github.com/emilk/egui/pull/4269) (thanks [@white-axe](https://github.com/white-axe)!) +* Remove a bunch of `unwrap()` [#4285](https://github.com/emilk/egui/pull/4285) + +#### Web +* Fix blurry rendering in some browsers [#4299](https://github.com/emilk/egui/pull/4299) +* Correctly identify if the browser tab has focus [#4280](https://github.com/emilk/egui/pull/4280) + + ## 0.27.1 - 2024-03-29 * Web: repaint if the `#hash` in the URL changes [#4261](https://github.com/emilk/egui/pull/4261) * Add web support for `zoom_factor` [#4260](https://github.com/emilk/egui/pull/4260) (thanks [@justusdieckmann](https://github.com/justusdieckmann)!) diff --git a/crates/egui-wgpu/CHANGELOG.md b/crates/egui-wgpu/CHANGELOG.md index 9dea60d1e0a..48aa5cdb454 100644 --- a/crates/egui-wgpu/CHANGELOG.md +++ b/crates/egui-wgpu/CHANGELOG.md @@ -6,6 +6,10 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.27.2 - 2024-04-02 +* Nothing new + + ## 0.27.1 - 2024-03-29 * Nothing new diff --git a/crates/egui-winit/CHANGELOG.md b/crates/egui-winit/CHANGELOG.md index 52dd56e8fdd..20662752609 100644 --- a/crates/egui-winit/CHANGELOG.md +++ b/crates/egui-winit/CHANGELOG.md @@ -5,6 +5,10 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.27.2 - 2024-04-02 +* Fix continuous repaint on Wayland when TextEdit is focused or IME output is set [#4269](https://github.com/emilk/egui/pull/4269) (thanks [@white-axe](https://github.com/white-axe)!) + + ## 0.27.1 - 2024-03-29 * Nothing new diff --git a/crates/egui_extras/CHANGELOG.md b/crates/egui_extras/CHANGELOG.md index 0980768aee2..cc00e7e2324 100644 --- a/crates/egui_extras/CHANGELOG.md +++ b/crates/egui_extras/CHANGELOG.md @@ -5,6 +5,10 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.27.2 - 2024-04-02 +* Nothing new + + ## 0.27.1 - 2024-03-29 * Nothing new diff --git a/crates/egui_glow/CHANGELOG.md b/crates/egui_glow/CHANGELOG.md index f39d8a199c5..627b1c2ebdb 100644 --- a/crates/egui_glow/CHANGELOG.md +++ b/crates/egui_glow/CHANGELOG.md @@ -6,6 +6,10 @@ Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.27.2 - 2024-04-02 +* Allow zoom/pan a plot as long as it contains the mouse cursor [#4292](https://github.com/emilk/egui/pull/4292) +* Prevent plot from resetting one axis while zooming/dragging the other [#4252](https://github.com/emilk/egui/pull/4252) (thanks [@YgorSouza](https://github.com/YgorSouza)!) +* egui_plot: Fix the same plot tick label being painted multiple times [#4307](https://github.com/emilk/egui/pull/4307) + + ## 0.27.1 - 2024-03-29 * Nothing new diff --git a/crates/epaint/CHANGELOG.md b/crates/epaint/CHANGELOG.md index b5fc33f6731..a4f935a3883 100644 --- a/crates/epaint/CHANGELOG.md +++ b/crates/epaint/CHANGELOG.md @@ -5,6 +5,10 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.27.2 - 2024-04-02 +* Nothing new + + ## 0.27.1 - 2024-03-29 * Fix visual glitch on the right side of highly rounded rectangles [#4244](https://github.com/emilk/egui/pull/4244) * Prevent visual glitch when shadow blur width is very high [#4245](https://github.com/emilk/egui/pull/4245) From 2342788973120552c012f028bfda576fea87738b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustav=20S=C3=B6rn=C3=A4s?= Date: Wed, 3 Apr 2024 10:02:29 +0200 Subject: [PATCH 091/134] Fix wrong replacement function in deprecation notice of `drag_released*` (#4314) Quick thing I noticed while updating a crate to egui 0.27. --- crates/egui/src/response.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index 18598f81a66..1bee1b29860 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -361,13 +361,13 @@ impl Response { /// The widget was being dragged, but now it has been released. #[inline] - #[deprecated = "Renamed 'dragged_stopped'"] + #[deprecated = "Renamed 'drag_stopped'"] pub fn drag_released(&self) -> bool { self.drag_stopped } /// The widget was being dragged by the button, but now it has been released. - #[deprecated = "Renamed 'dragged_stopped_by'"] + #[deprecated = "Renamed 'drag_stopped_by'"] pub fn drag_released_by(&self, button: PointerButton) -> bool { self.drag_stopped_by(button) } From 78d95f430b09e3040d3f383fde8fea2ae1592afb Mon Sep 17 00:00:00 2001 From: Juan Campa Date: Fri, 5 Apr 2024 01:31:33 -0400 Subject: [PATCH 092/134] Consider layer transform when positioning text agent (#4319) When positioning the text agent, the layer transform was not being considered. This not only caused issues with IME input positioning but also layout shifts if the text agent was off-screen. Before ![Screenshot 2024-04-04 at 13 33 11@2x](https://github.com/emilk/egui/assets/1410520/5d88a358-67bd-478c-95c9-d76f84d57c39) After ![Screenshot 2024-04-04 at 13 31 52@2x](https://github.com/emilk/egui/assets/1410520/55f068c7-56fe-4ba4-8455-7d0f613e8a52) --- crates/egui/src/widgets/text_edit/builder.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index e0cf2d012f1..f022e860daf 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -700,11 +700,15 @@ impl<'t> TextEdit<'t> { ); } - // For IME, so only set it when text is editable and visible! + // Set IME output (in screen coords) when text is editable and visible + let transform = ui + .memory(|m| m.layer_transforms.get(&ui.layer_id()).cloned()) + .unwrap_or_default(); + ui.ctx().output_mut(|o| { o.ime = Some(crate::output::IMEOutput { - rect, - cursor_rect: primary_cursor_rect, + rect: transform * rect, + cursor_rect: transform * primary_cursor_rect, }); }); } From 79fbd17b338c22e09a44517407ecb7490b3ee69d Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 18 Apr 2024 16:08:23 +0200 Subject: [PATCH 093/134] Add link to layouting tracking issue --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 2f4d5d19a83..924d509bd80 100644 --- a/README.md +++ b/README.md @@ -255,6 +255,8 @@ You can also call the layout code twice (once to get the size, once to do the in For "atomic" widgets (e.g. a button) `egui` knows the size before showing it, so centering buttons, labels etc is possible in `egui` without any special workarounds. +See [this issue](https://github.com/emilk/egui/issues/4378) for more. + #### CPU usage Since an immediate mode GUI does a full layout each frame, the layout code needs to be quick. If you have a very complex GUI this can tax the CPU. In particular, having a very large UI in a scroll area (with very long scrollback) can be slow, as the content needs to be laid out each frame. From c630a8de894b28bf1774f798b1b1fe90a759ee42 Mon Sep 17 00:00:00 2001 From: Juan Campa Date: Sun, 21 Apr 2024 04:58:40 -0400 Subject: [PATCH 094/134] Fix incorrect line breaks (#4377) While breaking a paragraph, it was possible to lose line break candidates that could've been used on the next line, causing egui to unnecessarily overrun `wrap.max_width`. This PR fixes it so that we don't forget about those candidates. Before: Note that the window can't resize to the requested width because the text is not wrapping. https://github.com/emilk/egui/assets/1410520/6430a334-2995-4b40-bc34-8f01923f9f95 After: https://github.com/emilk/egui/assets/1410520/225fa4cd-cbbb-4a7e-9580-7f1814c05ee7 --------- Co-authored-by: Emil Ernerfeldt --- crates/epaint/src/text/text_layout.rs | 31 ++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/crates/epaint/src/text/text_layout.rs b/crates/epaint/src/text/text_layout.rs index 322026da3c5..c9956aac197 100644 --- a/crates/epaint/src/text/text_layout.rs +++ b/crates/epaint/src/text/text_layout.rs @@ -296,7 +296,7 @@ fn line_break(paragraph: &Paragraph, job: &LayoutJob, out_rows: &mut Vec, e // Start a new row: row_start_idx = last_kept_index + 1; row_start_x = paragraph.glyphs[row_start_idx].pos.x; - row_break_candidates = Default::default(); + row_break_candidates.forget_before_idx(row_start_idx); } else { // Found no place to break, so we have to overrun wrap_width. } @@ -943,6 +943,35 @@ impl RowBreakCandidates { .or(self.any) } } + + fn forget_before_idx(&mut self, index: usize) { + let Self { + space, + cjk, + pre_cjk, + dash, + punctuation, + any, + } = self; + if space.map_or(false, |s| s < index) { + *space = None; + } + if cjk.map_or(false, |s| s < index) { + *cjk = None; + } + if pre_cjk.map_or(false, |s| s < index) { + *pre_cjk = None; + } + if dash.map_or(false, |s| s < index) { + *dash = None; + } + if punctuation.map_or(false, |s| s < index) { + *punctuation = None; + } + if any.map_or(false, |s| s < index) { + *any = None; + } + } } #[inline] From 89d7f9f9d3237b5d02d6bce87f6804d969b7e8f1 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 21 Apr 2024 11:05:44 +0200 Subject: [PATCH 095/134] Update cargo-deny and some dependencies (#4386) * Closes https://github.com/emilk/egui/issues/4382 --- Cargo.lock | 120 ++++++++++++++++++++++++++++++++++++++++------------- deny.toml | 55 ++++++++++++++++-------- 2 files changed, 129 insertions(+), 46 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c6d8727188a..112c7d78ef1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2286,12 +2286,9 @@ dependencies = [ [[package]] name = "line-wrap" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30344350a2a51da54c1d53be93fade8a237e545dbcc4bdbe635413f2117cab9" -dependencies = [ - "safemem", -] +checksum = "dd1bc4d24ad230d21fb898d1116b1801d7adfc449d42026475862ab48b11e70e" [[package]] name = "linked-hash-map" @@ -2798,9 +2795,9 @@ checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "plist" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5699cc8a63d1aa2b1ee8e12b9ad70ac790d65788cd36101fa37f87ea46c4cef" +checksum = "d9d34169e64b3c7a80c8621a48adaf44e0cf62c78a9b25dd9dd35f1881a17cf9" dependencies = [ "base64", "indexmap", @@ -3158,17 +3155,17 @@ dependencies = [ [[package]] name = "ring" -version = "0.16.20" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", + "cfg-if", + "getrandom", "libc", - "once_cell", "spin", "untrusted", - "web-sys", - "winapi", + "windows-sys 0.52.0", ] [[package]] @@ -3230,9 +3227,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.7" +version = "0.21.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" +checksum = "7fecbfb7b1444f477b345853b1fce097a2c6fb637b2bfb87e6bc5db0f043fae4" dependencies = [ "log", "ring", @@ -3242,9 +3239,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.101.6" +version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c7d5dece342910d9ba34d259310cae3e0154b873b35408b787b59bce53d34fe" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ "ring", "untrusted", @@ -3256,12 +3253,6 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" -[[package]] -name = "safemem" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" - [[package]] name = "same-file" version = "1.0.6" @@ -3305,9 +3296,9 @@ dependencies = [ [[package]] name = "sct" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ "ring", "untrusted", @@ -3507,9 +3498,9 @@ dependencies = [ [[package]] name = "spin" -version = "0.5.2" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] name = "spirv" @@ -3898,9 +3889,9 @@ checksum = "446c96c6dd42604779487f0a981060717156648c1706aa1f464677f03c6cc059" [[package]] name = "untrusted" -version = "0.7.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "ureq" @@ -4444,6 +4435,15 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.5", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -4474,6 +4474,22 @@ dependencies = [ "windows_x86_64_msvc 0.48.5", ] +[[package]] +name = "windows-targets" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -4486,6 +4502,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -4498,6 +4520,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -4510,6 +4538,18 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -4522,6 +4562,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -4534,6 +4580,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -4546,6 +4598,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -4558,6 +4616,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" + [[package]] name = "winit" version = "0.29.10" diff --git a/deny.toml b/deny.toml index 17c47772833..ebe372744dd 100644 --- a/deny.toml +++ b/deny.toml @@ -1,7 +1,17 @@ -# https://embarkstudios.github.io/cargo-deny/ +# Copied from https://github.com/rerun-io/rerun_template +# +# https://github.com/EmbarkStudios/cargo-deny +# +# cargo-deny checks our dependency tree for copy-left licenses, +# duplicate dependencies, and rustsec advisories (https://rustsec.org/advisories). +# +# Install: `cargo install cargo-deny` +# Check: `cargo deny check`. + # Note: running just `cargo deny check` without a `--target` can result in # false positives due to https://github.com/EmbarkStudios/cargo-deny/issues/324 +[graph] targets = [ { triple = "aarch64-apple-darwin" }, { triple = "i686-pc-windows-gnu" }, @@ -15,26 +25,29 @@ targets = [ { triple = "x86_64-unknown-linux-musl" }, { triple = "x86_64-unknown-redox" }, ] +all-features = true + [advisories] -vulnerability = "deny" -unmaintained = "warn" -yanked = "deny" +version = 2 ignore = [ - "RUSTSEC-2020-0071", # https://rustsec.org/advisories/RUSTSEC-2020-0071 - chrono/time: Potential segfault in the time crate + "RUSTSEC-2024-0320", # unmaintaines yaml-rust pulled in by syntect + { name = "async-process" }, # yanked crated pulled in by old accesskit ] + [bans] multiple-versions = "deny" -wildcards = "allow" # at least until https://github.com/EmbarkStudios/cargo-deny/issues/241 is fixed +wildcards = "deny" deny = [ - { name = "cmake" }, # Lord no - { name = "openssl-sys" }, # prefer rustls - { name = "openssl" }, # prefer rustls + { name = "cmake", reason = "It has hurt me too much" }, + { name = "openssl-sys", reason = "Use rustls" }, + { name = "openssl", reason = "Use rustls" }, ] skip = [ { name = "bitflags" }, # old 1.0 version via glutin, png, spirv, … + { name = "event-listener" }, # TODO(emilk): rustls pulls in two versions of this 😭 { name = "libloading" }, # wgpu-hal itself depends on 0.8 while some of its dependencies, like ash and d3d12, depend on 0.7 { name = "memoffset" }, # tiny dependency { name = "quick-xml" }, # old version via wayland-scanner @@ -42,8 +55,8 @@ skip = [ { name = "spin" }, # old version via ring through rusttls and other libraries, newer for wgpu. { name = "time" }, # old version pulled in by unmaintianed crate 'chrono' { name = "windows" }, # old version via accesskit_windows - { name = "x11rb" }, # old version via arboard { name = "x11rb-protocol" }, # old version via arboard + { name = "x11rb" }, # old version via arboard ] skip-tree = [ { name = "criterion" }, # dev-dependency @@ -56,10 +69,9 @@ skip-tree = [ [licenses] -unlicensed = "deny" -allow-osi-fsf-free = "neither" -confidence-threshold = 0.92 # We want really high confidence when inferring licenses from text -copyleft = "deny" +version = 2 +private = { ignore = true } +confidence-threshold = 0.93 # We want really high confidence when inferring licenses from text allow = [ "Apache-2.0 WITH LLVM-exception", # https://spdx.org/licenses/LLVM-exception.html "Apache-2.0", # https://tldrlegal.com/license/apache-license-2.0-(apache-2.0) @@ -67,15 +79,17 @@ allow = [ "BSD-3-Clause", # https://tldrlegal.com/license/bsd-3-clause-license-(revised) "BSL-1.0", # https://tldrlegal.com/license/boost-software-license-1.0-explained "CC0-1.0", # https://creativecommons.org/publicdomain/zero/1.0/ - "ISC", # https://tldrlegal.com/license/-isc-license - "LicenseRef-UFL-1.0", # https://tldrlegal.com/license/ubuntu-font-license,-1.0 - no official SPDX, see https://github.com/emilk/egui/issues/2321 + "ISC", # https://www.tldrlegal.com/license/isc-license + "LicenseRef-UFL-1.0", # no official SPDX, see https://github.com/emilk/egui/issues/2321 + "MIT-0", # https://choosealicense.com/licenses/mit-0/ "MIT", # https://tldrlegal.com/license/mit-license - "MPL-2.0", # https://www.mozilla.org/en-US/MPL/2.0/FAQ/ - see Q11 + "MPL-2.0", # https://www.mozilla.org/en-US/MPL/2.0/FAQ/ - see Q11. Used by webpki-roots on Linux. "OFL-1.1", # https://spdx.org/licenses/OFL-1.1.html - "OpenSSL", # https://www.openssl.org/source/license.html + "OpenSSL", # https://www.openssl.org/source/license.html - used on Linux "Unicode-DFS-2016", # https://spdx.org/licenses/Unicode-DFS-2016.html "Zlib", # https://tldrlegal.com/license/zlib-libpng-license-(zlib) ] +exceptions = [] [[licenses.clarify]] name = "webpki" @@ -86,3 +100,8 @@ license-files = [{ path = "LICENSE", hash = 0x001c7e6c }] name = "ring" expression = "MIT AND ISC AND OpenSSL" license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }] + + +[sources] +unknown-registry = "deny" +unknown-git = "deny" From 26c97a19a414f7b2060ead74b734ba71724b292c Mon Sep 17 00:00:00 2001 From: Narcha <42248344+Narcha@users.noreply.github.com> Date: Sun, 21 Apr 2024 11:05:53 +0200 Subject: [PATCH 096/134] Expose `ClosestElem` and `PlotConfig` (#4380) These two items are needed to implement the `PlotItem` trait (which is already public) when using `PlotGeometry::Rects`. Specifically, they are used in the return type of `PlotItem::find_closest` and as arguments to `PlotItem::on_hover`, which need to be implemented when using `PlotGeometry::Rects`. --- crates/egui_plot/src/items/mod.rs | 7 ++++--- crates/egui_plot/src/lib.rs | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/crates/egui_plot/src/items/mod.rs b/crates/egui_plot/src/items/mod.rs index 78f4560c6d3..ddf09b98504 100644 --- a/crates/egui_plot/src/items/mod.rs +++ b/crates/egui_plot/src/items/mod.rs @@ -9,11 +9,12 @@ use crate::*; use super::{Cursor, LabelFormatter, PlotBounds, PlotTransform}; use rect_elem::*; -use values::ClosestElem; pub use bar::Bar; pub use box_elem::{BoxElem, BoxSpread}; -pub use values::{LineStyle, MarkerShape, Orientation, PlotGeometry, PlotPoint, PlotPoints}; +pub use values::{ + ClosestElem, LineStyle, MarkerShape, Orientation, PlotGeometry, PlotPoint, PlotPoints, +}; mod bar; mod box_elem; @@ -45,7 +46,7 @@ pub trait PlotItem { fn highlighted(&self) -> bool; - /// Can the user hover this is item? + /// Can the user hover this item? fn allow_hover(&self) -> bool; fn geometry(&self) -> PlotGeometry<'_>; diff --git a/crates/egui_plot/src/lib.rs b/crates/egui_plot/src/lib.rs index a5adf1c11c1..9de1f7ce705 100644 --- a/crates/egui_plot/src/lib.rs +++ b/crates/egui_plot/src/lib.rs @@ -22,9 +22,9 @@ use epaint::{util::FloatOrd, Hsva}; pub use crate::{ axis::{Axis, AxisHints, HPlacement, Placement, VPlacement}, items::{ - Arrows, Bar, BarChart, BoxElem, BoxPlot, BoxSpread, HLine, Line, LineStyle, MarkerShape, - Orientation, PlotGeometry, PlotImage, PlotItem, PlotPoint, PlotPoints, Points, Polygon, - Text, VLine, + Arrows, Bar, BarChart, BoxElem, BoxPlot, BoxSpread, ClosestElem, HLine, Line, LineStyle, + MarkerShape, Orientation, PlotConfig, PlotGeometry, PlotImage, PlotItem, PlotPoint, + PlotPoints, Points, Polygon, Text, VLine, }, legend::{Corner, Legend}, memory::PlotMemory, From fe454573dbcc812302009d5e8ba97d7553d8c8a2 Mon Sep 17 00:00:00 2001 From: dataphract <86984145+dataphract@users.noreply.github.com> Date: Sun, 21 Apr 2024 04:07:55 -0500 Subject: [PATCH 097/134] Fix `hex_color!` macro by re-exporting `color_hex` crate from `ecolor` (#4372) The `hex_color!` macro currently makes an unqualified reference to the `color_hex` crate, which users may not have in their `Cargo.toml`. This PR re-exports `color_hex` from the root of `ecolor` and changes the macro to use the re-exported path. * Closes https://github.com/emilk/egui/issues/2644 --- crates/ecolor/src/hex_color_macro.rs | 2 +- crates/ecolor/src/lib.rs | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/ecolor/src/hex_color_macro.rs b/crates/ecolor/src/hex_color_macro.rs index 16f8cc2b8d8..a0a0729fdd1 100644 --- a/crates/ecolor/src/hex_color_macro.rs +++ b/crates/ecolor/src/hex_color_macro.rs @@ -13,7 +13,7 @@ #[macro_export] macro_rules! hex_color { ($s:literal) => {{ - let array = color_hex::color_from_hex!($s); + let array = $crate::color_hex::color_from_hex!($s); if array.len() == 3 { $crate::Color32::from_rgb(array[0], array[1], array[2]) } else { diff --git a/crates/ecolor/src/lib.rs b/crates/ecolor/src/lib.rs index e3c077edd50..a7072d8e89a 100644 --- a/crates/ecolor/src/lib.rs +++ b/crates/ecolor/src/lib.rs @@ -24,6 +24,9 @@ pub use hsva::*; #[cfg(feature = "color-hex")] mod hex_color_macro; +#[cfg(feature = "color-hex")] +#[doc(hidden)] +pub use color_hex; mod rgba; pub use rgba::*; From 5f9c17c85549833b13a55401a8de4295da475282 Mon Sep 17 00:00:00 2001 From: Varphone Wong Date: Sun, 21 Apr 2024 17:36:18 +0800 Subject: [PATCH 098/134] `egui`: Change `Ui::allocate_painter` to inherit properties from `Ui` (#4343) The painter, allocated by `Ui::allocate_painter`, doesn't inherit properties from the `Ui` object, leading to improper widget rendering in `egui`. Specifically, if the `Ui` object is disabled, the corresponding painter should also be disabled. --- crates/egui/src/ui.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 9ff05a267e8..d160f5d404d 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -1001,7 +1001,7 @@ impl Ui { pub fn allocate_painter(&mut self, desired_size: Vec2, sense: Sense) -> (Response, Painter) { let response = self.allocate_response(desired_size, sense); let clip_rect = self.clip_rect().intersect(response.rect); // Make sure we don't paint out of bounds - let painter = Painter::new(self.ctx().clone(), self.layer_id(), clip_rect); + let painter = self.painter().with_clip_rect(clip_rect); (response, painter) } From 690c3ba883efe6e3890f59f4ea40a25e7a9e963b Mon Sep 17 00:00:00 2001 From: Alexander Parlett Date: Sun, 21 Apr 2024 10:44:44 +0100 Subject: [PATCH 099/134] Use parent `Ui`s style for popups (#4325) * Closes --------- Co-authored-by: Alex Parlett --- .gitignore | 1 + crates/egui/src/containers/popup.rs | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index c43b95594e4..7db0b9d06fb 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ /.*.json /.vscode /media/* +.idea/ diff --git a/crates/egui/src/containers/popup.rs b/crates/egui/src/containers/popup.rs index 99165149f1d..9643a8eee31 100644 --- a/crates/egui/src/containers/popup.rs +++ b/crates/egui/src/containers/popup.rs @@ -333,13 +333,13 @@ pub fn popup_below_widget( /// # }); /// ``` pub fn popup_above_or_below_widget( - ui: &Ui, + parent_ui: &Ui, popup_id: Id, widget_response: &Response, above_or_below: AboveOrBelow, add_contents: impl FnOnce(&mut Ui) -> R, ) -> Option { - if ui.memory(|mem| mem.is_popup_open(popup_id)) { + if parent_ui.memory(|mem| mem.is_popup_open(popup_id)) { let (pos, pivot) = match above_or_below { AboveOrBelow::Above => (widget_response.rect.left_top(), Align2::LEFT_BOTTOM), AboveOrBelow::Below => (widget_response.rect.left_bottom(), Align2::LEFT_TOP), @@ -350,8 +350,8 @@ pub fn popup_above_or_below_widget( .constrain(true) .fixed_pos(pos) .pivot(pivot) - .show(ui.ctx(), |ui| { - let frame = Frame::popup(ui.style()); + .show(parent_ui.ctx(), |ui| { + let frame = Frame::popup(parent_ui.style()); let frame_margin = frame.total_margin(); frame .show(ui, |ui| { @@ -365,8 +365,8 @@ pub fn popup_above_or_below_widget( }) .inner; - if ui.input(|i| i.key_pressed(Key::Escape)) || widget_response.clicked_elsewhere() { - ui.memory_mut(|mem| mem.close_popup()); + if parent_ui.input(|i| i.key_pressed(Key::Escape)) || widget_response.clicked_elsewhere() { + parent_ui.memory_mut(|mem| mem.close_popup()); } Some(inner) } else { From d2c426924039fbd8b73be51dea01a5db0911238c Mon Sep 17 00:00:00 2001 From: rustbasic <127506429+rustbasic@users.noreply.github.com> Date: Sun, 21 Apr 2024 18:49:14 +0900 Subject: [PATCH 100/134] Fix : take `rounding` into account when using `Slider::trailing_fill` (#4308) Handles `rounding` when doing trailing_fill on the rail of `Slider`. We can see this by setting the rounding to around 8.0 ``` ui.visuals_mut().widgets.inactive.rounding = Rounding::same(8.0); ``` Before : There is a little bit of blue painted on the left end. ![20240404-2](https://github.com/emilk/egui/assets/127506429/aa70104c-0733-41c6-8e78-c3e69eb45204) After : Fix ![20240404-3](https://github.com/emilk/egui/assets/127506429/c2452fcb-48fd-4b2a-9f1a-02a3bf763ed1) --- crates/egui/src/widgets/slider.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/crates/egui/src/widgets/slider.rs b/crates/egui/src/widgets/slider.rs index a428fb7dc4d..f303464a4fe 100644 --- a/crates/egui/src/widgets/slider.rs +++ b/crates/egui/src/widgets/slider.rs @@ -686,12 +686,10 @@ impl<'a> Slider<'a> { let rail_radius = (spacing.slider_rail_height / 2.0).at_least(0.0); let rail_rect = self.rail_rect(rect, rail_radius); + let rounding = widget_visuals.inactive.rounding; - ui.painter().rect_filled( - rail_rect, - widget_visuals.inactive.rounding, - widget_visuals.inactive.bg_fill, - ); + ui.painter() + .rect_filled(rail_rect, rounding, widget_visuals.inactive.bg_fill); let position_1d = self.position_from_value(value, position_range); let center = self.marker_center(position_1d, &rail_rect); @@ -707,13 +705,17 @@ impl<'a> Slider<'a> { // The trailing rect has to be drawn differently depending on the orientation. match self.orientation { - SliderOrientation::Vertical => trailing_rail_rect.min.y = center.y, - SliderOrientation::Horizontal => trailing_rail_rect.max.x = center.x, + SliderOrientation::Horizontal => { + trailing_rail_rect.max.x = center.x + rounding.nw; + } + SliderOrientation::Vertical => { + trailing_rail_rect.min.y = center.y - rounding.se; + } }; ui.painter().rect_filled( trailing_rail_rect, - widget_visuals.inactive.rounding, + rounding, ui.visuals().selection.bg_fill, ); } From d68c8d70aa1dd5c44bd1a83a9f6aa8247e7c5c3b Mon Sep 17 00:00:00 2001 From: valadaptive <79560998+valadaptive@users.noreply.github.com> Date: Sun, 21 Apr 2024 06:23:59 -0400 Subject: [PATCH 101/134] Add a way to specify Undoer settings and construct Undoers more easily (#4357) * Closes https://github.com/emilk/egui/issues/4356 - Add `Undoer::new()` - This is necessary to construct an `Undoer` whose `State` parameter doesn't implement `Default`. - Add `Undoer::with_settings(...)` - This is necessary to actually pass settings into the `Undoer`. Without this, API consumers could construct their own `Settings` but not actually do anything with it. I've refrained from adding any kind of builder API for `Settings` because there are only three options and I don't want to duplicate or move all the documentation onto the builder methods. --- crates/egui/src/util/undoer.rs | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/crates/egui/src/util/undoer.rs b/crates/egui/src/util/undoer.rs index de6d2716171..d5f004f86ae 100644 --- a/crates/egui/src/util/undoer.rs +++ b/crates/egui/src/util/undoer.rs @@ -47,7 +47,7 @@ impl Default for Settings { /// /// Rule 1) will make sure an undo point is not created until you _stop_ dragging that slider. /// Rule 2) will make sure that you will get some undo points even if you are constantly changing the state. -#[derive(Clone, Default)] +#[derive(Clone)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct Undoer { settings: Settings, @@ -77,6 +77,21 @@ impl std::fmt::Debug for Undoer { } } +impl Default for Undoer +where + State: Clone + PartialEq, +{ + #[inline] + fn default() -> Self { + Self { + settings: Settings::default(), + undos: VecDeque::new(), + redos: Vec::new(), + flux: None, + } + } +} + /// Represents how the current state is changing #[derive(Clone)] struct Flux { @@ -89,6 +104,14 @@ impl Undoer where State: Clone + PartialEq, { + /// Create a new [`Undoer`] with the given [`Settings`]. + pub fn with_settings(settings: Settings) -> Self { + Self { + settings, + ..Default::default() + } + } + /// Do we have an undo point different from the given state? pub fn has_undo(&self, current_state: &State) -> bool { match self.undos.len() { From 46b241eb94f6bc9234c20d69159c5b4c5f784add Mon Sep 17 00:00:00 2001 From: YgorSouza <43298013+YgorSouza@users.noreply.github.com> Date: Sun, 21 Apr 2024 19:26:16 +0200 Subject: [PATCH 102/134] Add `xtask` crate (#4293) Replaces only the cargo_deny.sh script for now. Can be expanded over time to replace the other shell and python scripts, so only Rust is needed to work with the repository. Closes Closes --------- Co-authored-by: Emil Ernerfeldt --- .cargo/config.toml | 3 +++ Cargo.lock | 4 +++ Cargo.toml | 2 ++ scripts/cargo_deny.sh | 20 +-------------- xtask/Cargo.toml | 9 +++++++ xtask/README.md | 12 +++++++++ xtask/src/deny.rs | 60 +++++++++++++++++++++++++++++++++++++++++++ xtask/src/main.rs | 40 +++++++++++++++++++++++++++++ xtask/src/utils.rs | 45 ++++++++++++++++++++++++++++++++ 9 files changed, 176 insertions(+), 19 deletions(-) create mode 100644 xtask/Cargo.toml create mode 100644 xtask/README.md create mode 100644 xtask/src/deny.rs create mode 100644 xtask/src/main.rs create mode 100644 xtask/src/utils.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index be614b00236..b18b6ce735d 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -4,3 +4,6 @@ # we don't use `[build]` because of rust analyzer's build cache invalidation https://github.com/emilk/eframe_template/issues/93 [target.wasm32-unknown-unknown] rustflags = ["--cfg=web_sys_unstable_apis"] + +[alias] +xtask = "run --quiet --package xtask --" diff --git a/Cargo.lock b/Cargo.lock index 112c7d78ef1..8698db26e7a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4762,6 +4762,10 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" +[[package]] +name = "xtask" +version = "0.27.2" + [[package]] name = "yaml-rust" version = "0.4.5" diff --git a/Cargo.toml b/Cargo.toml index 598eae08cb1..425eb8de208 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,8 @@ members = [ "crates/epaint", "examples/*", + + "xtask", ] [workspace.package] diff --git a/scripts/cargo_deny.sh b/scripts/cargo_deny.sh index 560b8efd1bb..bf811e85204 100755 --- a/scripts/cargo_deny.sh +++ b/scripts/cargo_deny.sh @@ -1,21 +1,3 @@ #!/usr/bin/env bash -set -eu -script_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) -cd "$script_path/.." -set -x - -cargo install --quiet cargo-deny - -cargo deny --all-features --log-level error --target aarch64-apple-darwin check -cargo deny --all-features --log-level error --target aarch64-linux-android check -cargo deny --all-features --log-level error --target i686-pc-windows-gnu check -cargo deny --all-features --log-level error --target i686-pc-windows-msvc check -cargo deny --all-features --log-level error --target i686-unknown-linux-gnu check -cargo deny --all-features --log-level error --target wasm32-unknown-unknown check -cargo deny --all-features --log-level error --target x86_64-apple-darwin check -cargo deny --all-features --log-level error --target x86_64-pc-windows-gnu check -cargo deny --all-features --log-level error --target x86_64-pc-windows-msvc check -cargo deny --all-features --log-level error --target x86_64-unknown-linux-gnu check -cargo deny --all-features --log-level error --target x86_64-unknown-linux-musl check -cargo deny --all-features --log-level error --target x86_64-unknown-redox check +cargo xtask deny diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml new file mode 100644 index 00000000000..ba5b9382089 --- /dev/null +++ b/xtask/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "xtask" +edition.workspace = true +license.workspace = true +rust-version.workspace = true +version.workspace = true +publish = false + +[dependencies] diff --git a/xtask/README.md b/xtask/README.md new file mode 100644 index 00000000000..bb5e9277762 --- /dev/null +++ b/xtask/README.md @@ -0,0 +1,12 @@ +## xtask - Task automation + +This crate is meant to automate common tasks on the repository. It serves as a +replacement for shell scripts that is more portable across host operating +systems (namely Windows) and hopefully also easier to work with for +contributors who are already familiar with Rust (and not necessarily with shell +scripting). + +The executable can be invoked via the subcommand `cargo xtask`, thanks to an +alias defined in `.cargo/config.toml`. + +For more information, see . diff --git a/xtask/src/deny.rs b/xtask/src/deny.rs new file mode 100644 index 00000000000..5c24949bd43 --- /dev/null +++ b/xtask/src/deny.rs @@ -0,0 +1,60 @@ +//! Run `cargo deny` +//! +//! Also installs the subcommand if it is not already installed. + +use std::process::Command; + +use super::DynError; + +pub fn deny(args: &[&str]) -> Result<(), DynError> { + if !args.is_empty() { + return Err(format!("Invalid arguments: {args:?}").into()); + } + install_cargo_deny()?; + let targets = [ + "aarch64-apple-darwin", + "aarch64-linux-android", + "i686-pc-windows-gnu", + "i686-pc-windows-msvc", + "i686-unknown-linux-gnu", + "wasm32-unknown-unknown", + "x86_64-apple-darwin", + "x86_64-pc-windows-gnu", + "x86_64-pc-windows-msvc", + "x86_64-unknown-linux-gnu", + "x86_64-unknown-linux-musl", + "x86_64-unknown-redox", + ]; + for target in targets { + let mut cmd = Command::new("cargo"); + cmd.args([ + "deny", + "--all-features", + "--log-level", + "error", + "--target", + target, + "check", + ]); + super::utils::print_cmd(&cmd); + let status = cmd.status()?; + if !status.success() { + return Err(status.to_string().into()); + } + } + Ok(()) +} + +fn install_cargo_deny() -> Result<(), DynError> { + let already_installed = Command::new("cargo") + .args(["deny", "--version"]) + .output() + .is_ok_and(|out| out.status.success()); + if already_installed { + return Ok(()); + } + let mut cmd = Command::new("cargo"); + cmd.args(["+stable", "install", "--quiet", "--locked", "cargo-deny"]); + let reason = "install cargo-deny"; + super::utils::ask_to_run(cmd, true, reason) +} diff --git a/xtask/src/main.rs b/xtask/src/main.rs new file mode 100644 index 00000000000..b194629203a --- /dev/null +++ b/xtask/src/main.rs @@ -0,0 +1,40 @@ +#![allow(clippy::print_stdout)] +#![allow(clippy::print_stderr)] +#![allow(clippy::exit)] + +mod deny; +pub(crate) mod utils; + +type DynError = Box; + +fn main() { + if let Err(e) = try_main() { + eprintln!("{e}"); + std::process::exit(-1); + } +} + +fn try_main() -> Result<(), DynError> { + let arg_strings: Vec<_> = std::env::args().skip(1).collect(); + let args: Vec<_> = arg_strings.iter().map(String::as_str).collect(); + + match args.as_slice() { + &[] | &["-h"] | &["--help"] => print_help(), + &["deny", ..] => deny::deny(&args[1..])?, + c => Err(format!("Invalid arguments {c:?}"))?, + } + Ok(()) +} + +fn print_help() { + let help = " + xtask help + + Subcommands + deny: Run cargo-deny for all targets + + Options + -h, --help: print help and exit + "; + println!("{help}"); +} diff --git a/xtask/src/utils.rs b/xtask/src/utils.rs new file mode 100644 index 00000000000..760c27f8f14 --- /dev/null +++ b/xtask/src/utils.rs @@ -0,0 +1,45 @@ +use std::{ + env, + io::{self, Write as _}, + process::Command, +}; + +use super::DynError; + +/// Print the command and its arguments as if the user had typed them +pub fn print_cmd(cmd: &Command) { + print!("{} ", cmd.get_program().to_string_lossy()); + for arg in cmd.get_args() { + print!("{} ", arg.to_string_lossy()); + } + println!(); +} + +/// Prompt user before running a command +/// +/// Adapted from [miri](https://github.com/rust-lang/miri/blob/dba35d2be72f4b78343d1a0f0b4737306f310672/cargo-miri/src/util.rs#L181-L204) +pub fn ask_to_run(mut cmd: Command, ask: bool, reason: &str) -> Result<(), DynError> { + // Disable interactive prompts in CI (GitHub Actions, Travis, AppVeyor, etc). + // Azure doesn't set `CI` though (nothing to see here, just Microsoft being Microsoft), + // so we also check their `TF_BUILD`. + let is_ci = env::var_os("CI").is_some() || env::var_os("TF_BUILD").is_some(); + if ask && !is_ci { + let mut buf = String::new(); + print!("The script is going to run: \n\n`{cmd:?}`\n\n To {reason}.\nProceed? [Y/n] ",); + io::stdout().flush().unwrap(); + io::stdin().read_line(&mut buf).unwrap(); + match buf.trim().to_lowercase().as_ref() { + "" | "y" | "yes" => {} + "n" | "no" => return Err("Aborting as per your request".into()), + a => return Err(format!("Invalid answer `{a}`").into()), + }; + } else { + eprintln!("Running `{cmd:?}` to {reason}."); + } + + let status = cmd.status()?; + if !status.success() { + return Err(format!("failed to {reason}: {status}").into()); + } + Ok(()) +} From 87b294534e4339fe7fe55f23b2b04df2e13f3f0e Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 21 Apr 2024 20:36:32 +0200 Subject: [PATCH 103/134] Add `emath::OrderedFloat` (moved from `epaint::util::OrderedFloat`) (#4389) It makes much more sense in `emath` --- Cargo.lock | 1 + crates/ecolor/src/rgba.rs | 1 + crates/egui/Cargo.toml | 1 + crates/egui/src/load.rs | 20 ++++----- crates/egui/src/widgets/image.rs | 8 ++-- crates/egui_plot/src/items/mod.rs | 2 +- crates/egui_plot/src/lib.rs | 3 +- crates/emath/src/lib.rs | 4 +- .../src/util => emath/src}/ordered_float.rs | 41 +++++++++++-------- crates/epaint/src/lib.rs | 26 ------------ crates/epaint/src/stroke.rs | 2 +- crates/epaint/src/text/fonts.rs | 23 ++--------- crates/epaint/src/text/text_layout_types.rs | 10 ++--- crates/epaint/src/util/mod.rs | 5 +-- 14 files changed, 57 insertions(+), 90 deletions(-) rename crates/{epaint/src/util => emath/src}/ordered_float.rs (79%) diff --git a/Cargo.lock b/Cargo.lock index 8698db26e7a..d64d54397f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1242,6 +1242,7 @@ dependencies = [ "ahash", "backtrace", "document-features", + "emath", "epaint", "log", "nohash-hasher", diff --git a/crates/ecolor/src/rgba.rs b/crates/ecolor/src/rgba.rs index 2a61d311ee4..36cb33bd599 100644 --- a/crates/ecolor/src/rgba.rs +++ b/crates/ecolor/src/rgba.rs @@ -26,6 +26,7 @@ impl std::ops::IndexMut for Rgba { } } +/// Deterministically hash an `f32`, treating all NANs as equal, and ignoring the sign of zero. #[inline] pub(crate) fn f32_hash(state: &mut H, f: f32) { if f == 0.0 { diff --git a/crates/egui/Cargo.toml b/crates/egui/Cargo.toml index 347dbeda7cb..87b25a7ea4b 100644 --- a/crates/egui/Cargo.toml +++ b/crates/egui/Cargo.toml @@ -81,6 +81,7 @@ unity = ["epaint/unity"] [dependencies] +emath = { workspace = true, default-features = false } epaint = { workspace = true, default-features = false } ahash.workspace = true diff --git a/crates/egui/src/load.rs b/crates/egui/src/load.rs index 4d527de63be..71d9d086f56 100644 --- a/crates/egui/src/load.rs +++ b/crates/egui/src/load.rs @@ -55,23 +55,21 @@ mod bytes_loader; mod texture_loader; -use std::borrow::Cow; -use std::fmt::Debug; -use std::ops::Deref; -use std::{fmt::Display, sync::Arc}; +use std::{ + borrow::Cow, + fmt::{Debug, Display}, + ops::Deref, + sync::Arc, +}; use ahash::HashMap; -use epaint::mutex::Mutex; -use epaint::util::FloatOrd; -use epaint::util::OrderedFloat; -use epaint::TextureHandle; -use epaint::{textures::TextureOptions, ColorImage, TextureId, Vec2}; +use emath::{Float, OrderedFloat}; +use epaint::{mutex::Mutex, textures::TextureOptions, ColorImage, TextureHandle, TextureId, Vec2}; use crate::Context; -pub use self::bytes_loader::DefaultBytesLoader; -pub use self::texture_loader::DefaultTextureLoader; +pub use self::{bytes_loader::DefaultBytesLoader, texture_loader::DefaultTextureLoader}; /// Represents a failed attempt at loading an image. #[derive(Clone, Debug)] diff --git a/crates/egui/src/widgets/image.rs b/crates/egui/src/widgets/image.rs index 945c497dd4e..15b9dcddb2e 100644 --- a/crates/egui/src/widgets/image.rs +++ b/crates/egui/src/widgets/image.rs @@ -1,12 +1,12 @@ use std::borrow::Cow; -use crate::load::TextureLoadResult; +use emath::{Float as _, Rot2}; +use epaint::RectShape; + use crate::{ - load::{Bytes, SizeHint, SizedTexture, TexturePoll}, + load::{Bytes, SizeHint, SizedTexture, TextureLoadResult, TexturePoll}, *, }; -use emath::Rot2; -use epaint::{util::FloatOrd, RectShape}; /// A widget which displays an image. /// diff --git a/crates/egui_plot/src/items/mod.rs b/crates/egui_plot/src/items/mod.rs index ddf09b98504..56098b36699 100644 --- a/crates/egui_plot/src/items/mod.rs +++ b/crates/egui_plot/src/items/mod.rs @@ -3,7 +3,7 @@ use std::ops::RangeInclusive; -use epaint::{emath::Rot2, util::FloatOrd, Mesh}; +use epaint::{emath::Rot2, Mesh}; use crate::*; diff --git a/crates/egui_plot/src/lib.rs b/crates/egui_plot/src/lib.rs index 9de1f7ce705..2e31f8312f5 100644 --- a/crates/egui_plot/src/lib.rs +++ b/crates/egui_plot/src/lib.rs @@ -17,7 +17,8 @@ use std::{cmp::Ordering, ops::RangeInclusive, sync::Arc}; use egui::ahash::HashMap; use egui::*; -use epaint::{util::FloatOrd, Hsva}; +use emath::Float as _; +use epaint::Hsva; pub use crate::{ axis::{Axis, AxisHints, HPlacement, Placement, VPlacement}, diff --git a/crates/emath/src/lib.rs b/crates/emath/src/lib.rs index 911fb52a044..d3e2f5e0313 100644 --- a/crates/emath/src/lib.rs +++ b/crates/emath/src/lib.rs @@ -30,6 +30,7 @@ use std::ops::{Add, Div, Mul, RangeInclusive, Sub}; pub mod align; mod history; mod numeric; +mod ordered_float; mod pos2; mod range; mod rect; @@ -40,10 +41,11 @@ mod ts_transform; mod vec2; mod vec2b; -pub use { +pub use self::{ align::{Align, Align2}, history::History, numeric::*, + ordered_float::*, pos2::*, range::Rangef, rect::*, diff --git a/crates/epaint/src/util/ordered_float.rs b/crates/emath/src/ordered_float.rs similarity index 79% rename from crates/epaint/src/util/ordered_float.rs rename to crates/emath/src/ordered_float.rs index 72f06a20a39..950ec16ac2c 100644 --- a/crates/epaint/src/util/ordered_float.rs +++ b/crates/emath/src/ordered_float.rs @@ -7,9 +7,12 @@ use std::hash::{Hash, Hasher}; /// Wraps a floating-point value to add total order and hash. /// Possible types for `T` are `f32` and `f64`. /// -/// See also [`FloatOrd`]. +/// All NaNs are considered equal to each other. +/// The size of zero is ignored. +/// +/// See also [`Float`]. #[derive(Clone, Copy)] -pub struct OrderedFloat(T); +pub struct OrderedFloat(pub T); impl OrderedFloat { #[inline] @@ -68,44 +71,34 @@ impl From for OrderedFloat { /// /// Example with `f64`: /// ``` -/// use epaint::util::FloatOrd; +/// use emath::Float as _; /// /// let array = [1.0, 2.5, 2.0]; /// let max = array.iter().max_by_key(|val| val.ord()); /// /// assert_eq!(max, Some(&2.5)); /// ``` -pub trait FloatOrd { +pub trait Float: PartialOrd + PartialEq + private::FloatImpl { /// Type to provide total order, useful as key in sorted contexts. fn ord(self) -> OrderedFloat where Self: Sized; } -impl FloatOrd for f32 { +impl Float for f32 { #[inline] fn ord(self) -> OrderedFloat { OrderedFloat(self) } } -impl FloatOrd for f64 { +impl Float for f64 { #[inline] fn ord(self) -> OrderedFloat { OrderedFloat(self) } } -// ---------------------------------------------------------------------------- - -/// Internal abstraction over floating point types -#[doc(hidden)] -pub trait Float: PartialOrd + PartialEq + private::FloatImpl {} - -impl Float for f32 {} - -impl Float for f64 {} - // Keep this trait in private module, to avoid exposing its methods as extensions in user code mod private { use super::*; @@ -124,7 +117,13 @@ mod private { #[inline] fn hash(&self, state: &mut H) { - crate::f32_hash(state, *self); + if *self == 0.0 { + state.write_u8(0); + } else if self.is_nan() { + state.write_u8(1); + } else { + self.to_bits().hash(state); + } } } @@ -136,7 +135,13 @@ mod private { #[inline] fn hash(&self, state: &mut H) { - crate::f64_hash(state, *self); + if *self == 0.0 { + state.write_u8(0); + } else if self.is_nan() { + state.write_u8(1); + } else { + self.to_bits().hash(state); + } } } } diff --git a/crates/epaint/src/lib.rs b/crates/epaint/src/lib.rs index 18298484679..f7ee04cb75d 100644 --- a/crates/epaint/src/lib.rs +++ b/crates/epaint/src/lib.rs @@ -152,32 +152,6 @@ macro_rules! epaint_assert { } } -// ---------------------------------------------------------------------------- - -#[inline(always)] -pub(crate) fn f32_hash(state: &mut H, f: f32) { - if f == 0.0 { - state.write_u8(0); - } else if f.is_nan() { - state.write_u8(1); - } else { - use std::hash::Hash; - f.to_bits().hash(state); - } -} - -#[inline(always)] -pub(crate) fn f64_hash(state: &mut H, f: f64) { - if f == 0.0 { - state.write_u8(0); - } else if f.is_nan() { - state.write_u8(1); - } else { - use std::hash::Hash; - f.to_bits().hash(state); - } -} - // --------------------------------------------------------------------------- /// Was epaint compiled with the `rayon` feature? diff --git a/crates/epaint/src/stroke.rs b/crates/epaint/src/stroke.rs index 15ddd231f1b..9dafef31ddc 100644 --- a/crates/epaint/src/stroke.rs +++ b/crates/epaint/src/stroke.rs @@ -48,7 +48,7 @@ impl std::hash::Hash for Stroke { #[inline(always)] fn hash(&self, state: &mut H) { let Self { width, color } = *self; - crate::f32_hash(state, width); + emath::OrderedFloat(width).hash(state); color.hash(state); } } diff --git a/crates/epaint/src/text/fonts.rs b/crates/epaint/src/text/fonts.rs index 557d71733c8..394b898291e 100644 --- a/crates/epaint/src/text/fonts.rs +++ b/crates/epaint/src/text/fonts.rs @@ -8,7 +8,7 @@ use crate::{ }, TextureAtlas, }; -use emath::NumExt as _; +use emath::{NumExt as _, OrderedFloat}; // ---------------------------------------------------------------------------- @@ -56,7 +56,7 @@ impl std::hash::Hash for FontId { #[inline(always)] fn hash(&self, state: &mut H) { let Self { size, family } = self; - crate::f32_hash(state, *size); + emath::OrderedFloat(*size).hash(state); family.hash(state); } } @@ -567,21 +567,6 @@ impl FontsAndCache { // ---------------------------------------------------------------------------- -#[derive(Clone, Copy, Debug, PartialEq)] -struct HashableF32(f32); - -#[allow(clippy::derived_hash_with_manual_eq)] -impl std::hash::Hash for HashableF32 { - #[inline(always)] - fn hash(&self, state: &mut H) { - crate::f32_hash(state, self.0); - } -} - -impl Eq for HashableF32 {} - -// ---------------------------------------------------------------------------- - /// The collection of fonts used by `epaint`. /// /// Required in order to paint text. @@ -591,7 +576,7 @@ pub struct FontsImpl { definitions: FontDefinitions, atlas: Arc>, font_impl_cache: FontImplCache, - sized_family: ahash::HashMap<(HashableF32, FontFamily), Font>, + sized_family: ahash::HashMap<(OrderedFloat, FontFamily), Font>, } impl FontsImpl { @@ -641,7 +626,7 @@ impl FontsImpl { let FontId { size, family } = font_id; self.sized_family - .entry((HashableF32(*size), family.clone())) + .entry((OrderedFloat(*size), family.clone())) .or_insert_with(|| { let fonts = &self.definitions.families.get(family); let fonts = fonts diff --git a/crates/epaint/src/text/text_layout_types.rs b/crates/epaint/src/text/text_layout_types.rs index 70317f771f6..5e4a56d9502 100644 --- a/crates/epaint/src/text/text_layout_types.rs +++ b/crates/epaint/src/text/text_layout_types.rs @@ -185,7 +185,7 @@ impl std::hash::Hash for LayoutJob { text.hash(state); sections.hash(state); wrap.hash(state); - crate::f32_hash(state, *first_row_min_height); + emath::OrderedFloat(*first_row_min_height).hash(state); break_on_newline.hash(state); halign.hash(state); justify.hash(state); @@ -214,7 +214,7 @@ impl std::hash::Hash for LayoutSection { byte_range, format, } = self; - crate::f32_hash(state, *leading_space); + OrderedFloat(*leading_space).hash(state); byte_range.hash(state); format.hash(state); } @@ -293,9 +293,9 @@ impl std::hash::Hash for TextFormat { valign, } = self; font_id.hash(state); - crate::f32_hash(state, *extra_letter_spacing); + emath::OrderedFloat(*extra_letter_spacing).hash(state); if let Some(line_height) = *line_height { - crate::f32_hash(state, line_height); + emath::OrderedFloat(line_height).hash(state); } color.hash(state); background.hash(state); @@ -375,7 +375,7 @@ impl std::hash::Hash for TextWrapping { break_anywhere, overflow_character, } = self; - crate::f32_hash(state, *max_width); + emath::OrderedFloat(*max_width).hash(state); max_rows.hash(state); break_anywhere.hash(state); overflow_character.hash(state); diff --git a/crates/epaint/src/util/mod.rs b/crates/epaint/src/util/mod.rs index 2aa70ee041e..383b945671c 100644 --- a/crates/epaint/src/util/mod.rs +++ b/crates/epaint/src/util/mod.rs @@ -1,6 +1,5 @@ -mod ordered_float; - -pub use ordered_float::*; +#[deprecated = "Use emath::OrderedFloat instead"] +pub use emath::OrderedFloat; /// Hash the given value with a predictable hasher. #[inline] From a2f1ca31a085be1974d50102a82137a18b734483 Mon Sep 17 00:00:00 2001 From: bu5hm4nn Date: Mon, 22 Apr 2024 01:06:33 -0600 Subject: [PATCH 104/134] Add `ViewportCommand::RequestCut`, `RequestCopy` and `RequestPaste` to trigger Clipboard actions (#4035) ### Motivation We want to offer our users a context menu with `Cut`, `Copy` and `Paste` actions. `Paste` is not possible out of the box. ### Changes This PR adds `ViewportCommand::RequestCut`, `ViewportCommand::RequestCopy` and `ViewportCommand::RequestPaste`. They are routed and handled after the example of `ViewportCommand::Screenshot` and result in the same code being executed as when the user uses `CTRL+V` style keyboard commands. ### Reasoning In our last release we used an instance of `egui_winit::clipboard::Clipboard` in order to get the `Paste` functionality. However Linux users on Wayland complained about broken clipboard interaction (https://github.com/mikedilger/gossip/issues/617). After a while of digging I could not find the issue although I have found references to problems with multiple clipboards per handle before (https://gitlab.gnome.org/GNOME/mutter/-/issues/1250) but I compared mutter with weston and the problem occured on both. So to solve this I set out to extend egui to access the clipboard instance already present in egui_winit. Since there was no trivial way to reach the instance of `egui_winit::State` I felt the best approach was to follow the logic of the new `ViewportCommand::Screenshot`. ### Variations It could make sense to make the introduced `enum ActionRequested` a part of crates/egui/src/viewport.rs and to then wrap them into one single `ViewportCommand::ActionRequest(ActionRequested)`. ### Example ```Rust let mut text = String::new(); let response = ui.text_edit_singleline(&mut text); if ui.button("Paste").clicked() { response.request_focus(); ui.ctx().send_viewport_cmd(ViewportCommand::RequestPaste); } ``` --------- Co-authored-by: Emil Ernerfeldt --- crates/eframe/src/native/glow_integration.rs | 55 ++++++++++++++------ crates/eframe/src/native/wgpu_integration.rs | 45 +++++++++++++--- crates/egui-winit/src/lib.rs | 28 ++++++++-- crates/egui/src/viewport.rs | 15 ++++++ crates/egui_glow/src/winit.rs | 10 ++-- 5 files changed, 119 insertions(+), 34 deletions(-) diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index 7c9eada1ccc..880b906c2b8 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -9,6 +9,7 @@ use std::{cell::RefCell, num::NonZeroU32, rc::Rc, sync::Arc, time::Instant}; +use egui_winit::ActionRequested; use glutin::{ config::GlConfig, context::NotCurrentGlContext, @@ -22,8 +23,9 @@ use winit::{ }; use egui::{ - epaint::ahash::HashMap, DeferredViewportUiCallback, ImmediateViewport, ViewportBuilder, - ViewportClass, ViewportId, ViewportIdMap, ViewportIdPair, ViewportInfo, ViewportOutput, + ahash::HashSet, epaint::ahash::HashMap, DeferredViewportUiCallback, ImmediateViewport, + ViewportBuilder, ViewportClass, ViewportId, ViewportIdMap, ViewportIdPair, ViewportInfo, + ViewportOutput, }; #[cfg(feature = "accesskit")] use egui_winit::accesskit_winit; @@ -104,7 +106,7 @@ struct Viewport { builder: ViewportBuilder, deferred_commands: Vec, info: ViewportInfo, - screenshot_requested: bool, + actions_requested: HashSet, /// The user-callback that shows the ui. /// None for immediate viewports. @@ -682,17 +684,38 @@ impl GlowWinitRunning { ); { - let screenshot_requested = std::mem::take(&mut viewport.screenshot_requested); - if screenshot_requested { - let screenshot = painter.read_screen_rgba(screen_size_in_pixels); - egui_winit - .egui_input_mut() - .events - .push(egui::Event::Screenshot { - viewport_id, - image: screenshot.into(), - }); + for action in viewport.actions_requested.drain() { + match action { + ActionRequested::Screenshot => { + let screenshot = painter.read_screen_rgba(screen_size_in_pixels); + egui_winit + .egui_input_mut() + .events + .push(egui::Event::Screenshot { + viewport_id, + image: screenshot.into(), + }); + } + ActionRequested::Cut => { + egui_winit.egui_input_mut().events.push(egui::Event::Cut); + } + ActionRequested::Copy => { + egui_winit.egui_input_mut().events.push(egui::Event::Copy); + } + ActionRequested::Paste => { + if let Some(contents) = egui_winit.clipboard_text() { + let contents = contents.replace("\r\n", "\n"); + if !contents.is_empty() { + egui_winit + .egui_input_mut() + .events + .push(egui::Event::Paste(contents)); + } + } + } + } } + integration.post_rendering(&window); } @@ -1020,7 +1043,7 @@ impl GlutinWindowContext { builder: viewport_builder, deferred_commands: vec![], info, - screenshot_requested: false, + actions_requested: Default::default(), viewport_ui_cb: None, gl_surface: None, window: window.map(Arc::new), @@ -1277,7 +1300,7 @@ impl GlutinWindowContext { std::mem::take(&mut viewport.deferred_commands), window, is_viewport_focused, - &mut viewport.screenshot_requested, + &mut viewport.actions_requested, ); // For Wayland : https://github.com/emilk/egui/issues/4196 @@ -1323,7 +1346,7 @@ fn initialize_or_update_viewport( builder, deferred_commands: vec![], info: Default::default(), - screenshot_requested: false, + actions_requested: Default::default(), viewport_ui_cb, window: None, egui_winit: None, diff --git a/crates/eframe/src/native/wgpu_integration.rs b/crates/eframe/src/native/wgpu_integration.rs index 9e0022489e7..1287ce8a01f 100644 --- a/crates/eframe/src/native/wgpu_integration.rs +++ b/crates/eframe/src/native/wgpu_integration.rs @@ -7,6 +7,7 @@ use std::{cell::RefCell, num::NonZeroU32, rc::Rc, sync::Arc, time::Instant}; +use egui_winit::ActionRequested; use parking_lot::Mutex; use raw_window_handle::{HasDisplayHandle as _, HasWindowHandle as _}; use winit::{ @@ -15,9 +16,9 @@ use winit::{ }; use egui::{ - ahash::HashMap, DeferredViewportUiCallback, FullOutput, ImmediateViewport, ViewportBuilder, - ViewportClass, ViewportId, ViewportIdMap, ViewportIdPair, ViewportIdSet, ViewportInfo, - ViewportOutput, + ahash::{HashMap, HashSet, HashSetExt}, + DeferredViewportUiCallback, FullOutput, ImmediateViewport, ViewportBuilder, ViewportClass, + ViewportId, ViewportIdMap, ViewportIdPair, ViewportIdSet, ViewportInfo, ViewportOutput, }; #[cfg(feature = "accesskit")] use egui_winit::accesskit_winit; @@ -78,7 +79,7 @@ pub struct Viewport { builder: ViewportBuilder, deferred_commands: Vec, info: ViewportInfo, - screenshot_requested: bool, + actions_requested: HashSet, /// `None` for sync viewports. viewport_ui_cb: Option>, @@ -289,7 +290,7 @@ impl WgpuWinitApp { builder, deferred_commands: vec![], info, - screenshot_requested: false, + actions_requested: Default::default(), viewport_ui_cb: None, window: Some(window), egui_winit: Some(egui_winit), @@ -676,7 +677,10 @@ impl WgpuWinitRunning { let clipped_primitives = egui_ctx.tessellate(shapes, pixels_per_point); - let screenshot_requested = std::mem::take(&mut viewport.screenshot_requested); + let screenshot_requested = viewport + .actions_requested + .take(&ActionRequested::Screenshot) + .is_some(); let (vsync_secs, screenshot) = painter.paint_and_update_textures( viewport_id, pixels_per_point, @@ -695,6 +699,31 @@ impl WgpuWinitRunning { }); } + for action in viewport.actions_requested.drain() { + match action { + ActionRequested::Screenshot => { + // already handled above + } + ActionRequested::Cut => { + egui_winit.egui_input_mut().events.push(egui::Event::Cut); + } + ActionRequested::Copy => { + egui_winit.egui_input_mut().events.push(egui::Event::Copy); + } + ActionRequested::Paste => { + if let Some(contents) = egui_winit.clipboard_text() { + let contents = contents.replace("\r\n", "\n"); + if !contents.is_empty() { + egui_winit + .egui_input_mut() + .events + .push(egui::Event::Paste(contents)); + } + } + } + } + } + integration.post_rendering(window); let active_viewports_ids: ViewportIdSet = viewport_output.keys().copied().collect(); @@ -1073,7 +1102,7 @@ fn handle_viewport_output( std::mem::take(&mut viewport.deferred_commands), window, is_viewport_focused, - &mut viewport.screenshot_requested, + &mut viewport.actions_requested, ); // For Wayland : https://github.com/emilk/egui/issues/4196 @@ -1120,7 +1149,7 @@ fn initialize_or_update_viewport( builder, deferred_commands: vec![], info: Default::default(), - screenshot_requested: false, + actions_requested: HashSet::new(), viewport_ui_cb, window: None, egui_winit: None, diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 5abfa0f6898..1bba39b3cc6 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -14,7 +14,9 @@ pub use accesskit_winit; pub use egui; #[cfg(feature = "accesskit")] use egui::accesskit; -use egui::{Pos2, Rect, Vec2, ViewportBuilder, ViewportCommand, ViewportId, ViewportInfo}; +use egui::{ + ahash::HashSet, Pos2, Rect, Vec2, ViewportBuilder, ViewportCommand, ViewportId, ViewportInfo, +}; pub use winit; pub mod clipboard; @@ -1254,6 +1256,13 @@ fn translate_cursor(cursor_icon: egui::CursorIcon) -> Option, window: &Window, is_viewport_focused: bool, - screenshot_requested: &mut bool, + actions_requested: &mut HashSet, ) { for command in commands { process_viewport_command( @@ -1270,7 +1279,7 @@ pub fn process_viewport_commands( command, info, is_viewport_focused, - screenshot_requested, + actions_requested, ); } } @@ -1281,7 +1290,7 @@ fn process_viewport_command( command: ViewportCommand, info: &mut ViewportInfo, is_viewport_focused: bool, - screenshot_requested: &mut bool, + actions_requested: &mut HashSet, ) { crate::profile_function!(); @@ -1478,7 +1487,16 @@ fn process_viewport_command( } } ViewportCommand::Screenshot => { - *screenshot_requested = true; + actions_requested.insert(ActionRequested::Screenshot); + } + ViewportCommand::RequestCut => { + actions_requested.insert(ActionRequested::Cut); + } + ViewportCommand::RequestCopy => { + actions_requested.insert(ActionRequested::Copy); + } + ViewportCommand::RequestPaste => { + actions_requested.insert(ActionRequested::Paste); } } } diff --git a/crates/egui/src/viewport.rs b/crates/egui/src/viewport.rs index aed8d35165e..ad7332877c0 100644 --- a/crates/egui/src/viewport.rs +++ b/crates/egui/src/viewport.rs @@ -1038,6 +1038,21 @@ pub enum ViewportCommand { /// /// The results are returned in `crate::Event::Screenshot`. Screenshot, + + /// Request cut of the current selection + /// + /// This is equivalent to the system keyboard shortcut for cut (e.g. CTRL + X). + RequestCut, + + /// Request a copy of the current selection. + /// + /// This is equivalent to the system keyboard shortcut for copy (e.g. CTRL + C). + RequestCopy, + + /// Request a paste from the clipboard to the current focused TextEdit if any. + /// + /// This is equivalent to the system keyboard shortcut for paste (e.g. CTRL + V). + RequestPaste, } impl ViewportCommand { diff --git a/crates/egui_glow/src/winit.rs b/crates/egui_glow/src/winit.rs index 5d87d000416..0c407ccb75d 100644 --- a/crates/egui_glow/src/winit.rs +++ b/crates/egui_glow/src/winit.rs @@ -1,7 +1,7 @@ pub use egui_winit; pub use egui_winit::EventResponse; -use egui::{ViewportId, ViewportOutput}; +use egui::{ahash::HashSet, ViewportId, ViewportOutput}; use egui_winit::winit; use crate::shader_version::ShaderVersion; @@ -79,17 +79,17 @@ impl EguiGlow { log::warn!("Multiple viewports not yet supported by EguiGlow"); } for (_, ViewportOutput { commands, .. }) in viewport_output { - let mut screenshot_requested = false; + let mut actions_requested: HashSet = Default::default(); egui_winit::process_viewport_commands( &self.egui_ctx, &mut self.viewport_info, commands, window, true, - &mut screenshot_requested, + &mut actions_requested, ); - if screenshot_requested { - log::warn!("Screenshot not yet supported by EguiGlow"); + for action in actions_requested { + log::warn!("{:?} not yet supported by EguiGlow", action); } } From 587bc2034a38d9de89dafa353734e4e4776931de Mon Sep 17 00:00:00 2001 From: zhatuokun <62168376+zhatuokun@users.noreply.github.com> Date: Mon, 22 Apr 2024 15:22:54 +0800 Subject: [PATCH 105/134] Fix `Panel` incorrect size (#4351) * Closes * Closes Previously, `SidePanel`/`TopBottomPanel` didn't include `inner_margin` when setting the min width/height of `Frame`. As a result, when your `Panel` has `inner_margin`, its size doesn't work as expected. Before ![before](https://github.com/emilk/egui/assets/62168376/3bd20c0a-6625-4786-9f7d-85449a0e436c) After ![after](https://github.com/emilk/egui/assets/62168376/a2e41ed4-4f20-484c-8b54-d2ce9cc71d95) --- crates/egui/src/containers/panel.rs | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/crates/egui/src/containers/panel.rs b/crates/egui/src/containers/panel.rs index 27be59996d4..ee41af87939 100644 --- a/crates/egui/src/containers/panel.rs +++ b/crates/egui/src/containers/panel.rs @@ -150,7 +150,7 @@ impl SidePanel { self } - /// The initial wrapping width of the [`SidePanel`]. + /// The initial wrapping width of the [`SidePanel`], including margins. #[inline] pub fn default_width(mut self, default_width: f32) -> Self { self.default_width = default_width; @@ -161,21 +161,21 @@ impl SidePanel { self } - /// Minimum width of the panel. + /// Minimum width of the panel, including margins. #[inline] pub fn min_width(mut self, min_width: f32) -> Self { self.width_range = Rangef::new(min_width, self.width_range.max.at_least(min_width)); self } - /// Maximum width of the panel. + /// Maximum width of the panel, including margins. #[inline] pub fn max_width(mut self, max_width: f32) -> Self { self.width_range = Rangef::new(self.width_range.min.at_most(max_width), max_width); self } - /// The allowable width range for the panel. + /// The allowable width range for the panel, including margins. #[inline] pub fn width_range(mut self, width_range: impl Into) -> Self { let width_range = width_range.into(); @@ -184,7 +184,7 @@ impl SidePanel { self } - /// Enforce this exact width. + /// Enforce this exact width, including margins. #[inline] pub fn exact_width(mut self, width: f32) -> Self { self.default_width = width; @@ -262,7 +262,9 @@ impl SidePanel { let frame = frame.unwrap_or_else(|| Frame::side_top_panel(ui.style())); let inner_response = frame.show(&mut panel_ui, |ui| { ui.set_min_height(ui.max_rect().height()); // Make sure the frame fills the full height - ui.set_min_width(width_range.min); + ui.set_min_width( + width_range.min - (frame.inner_margin.left + frame.inner_margin.right), + ); add_contents(ui) }); @@ -609,7 +611,7 @@ impl TopBottomPanel { self } - /// The initial height of the [`TopBottomPanel`]. + /// The initial height of the [`TopBottomPanel`], including margins. /// Defaults to [`style::Spacing::interact_size`].y. #[inline] pub fn default_height(mut self, default_height: f32) -> Self { @@ -621,21 +623,21 @@ impl TopBottomPanel { self } - /// Minimum height of the panel. + /// Minimum height of the panel, including margins. #[inline] pub fn min_height(mut self, min_height: f32) -> Self { self.height_range = Rangef::new(min_height, self.height_range.max.at_least(min_height)); self } - /// Maximum height of the panel. + /// Maximum height of the panel, including margins. #[inline] pub fn max_height(mut self, max_height: f32) -> Self { self.height_range = Rangef::new(self.height_range.min.at_most(max_height), max_height); self } - /// The allowable height range for the panel. + /// The allowable height range for the panel, including margins. #[inline] pub fn height_range(mut self, height_range: impl Into) -> Self { let height_range = height_range.into(); @@ -646,7 +648,7 @@ impl TopBottomPanel { self } - /// Enforce this exact height. + /// Enforce this exact height, including margins. #[inline] pub fn exact_height(mut self, height: f32) -> Self { self.default_height = Some(height); @@ -728,7 +730,9 @@ impl TopBottomPanel { let frame = frame.unwrap_or_else(|| Frame::side_top_panel(ui.style())); let inner_response = frame.show(&mut panel_ui, |ui| { ui.set_min_width(ui.max_rect().width()); // Make the frame fill full width - ui.set_min_height(height_range.min); + ui.set_min_height( + height_range.min - (frame.inner_margin.top + frame.inner_margin.bottom), + ); add_contents(ui) }); From 436c671331a80ac55120423f26fab069f4508770 Mon Sep 17 00:00:00 2001 From: rustbasic <127506429+rustbasic@users.noreply.github.com> Date: Mon, 22 Apr 2024 19:43:07 +0900 Subject: [PATCH 106/134] Improve IME support with new `Event::Ime` (#4358) * Closes #4354 Fix: can't repeat input chinese words AND For Windows : ImeEnable ImeDisable --------- Co-authored-by: Emil Ernerfeldt --- crates/eframe/src/web/text_agent.rs | 13 ++-- crates/egui-winit/src/lib.rs | 45 ++++++++---- crates/egui/src/data/input.rs | 29 +++++--- crates/egui/src/widgets/text_edit/builder.rs | 72 +++++++++++--------- crates/egui/src/widgets/text_edit/state.rs | 2 +- 5 files changed, 99 insertions(+), 62 deletions(-) diff --git a/crates/eframe/src/web/text_agent.rs b/crates/eframe/src/web/text_agent.rs index a879f99d008..c61b7093596 100644 --- a/crates/eframe/src/web/text_agent.rs +++ b/crates/eframe/src/web/text_agent.rs @@ -68,7 +68,8 @@ pub fn install_text_agent(runner_ref: &WebRunner) -> Result<(), JsValue> { is_composing.set(true); input_clone.set_value(""); - runner.input.raw.events.push(egui::Event::CompositionStart); + let egui_event = egui::Event::Ime(egui::ImeEvent::Enabled); + runner.input.raw.events.push(egui_event); runner.needs_repaint.repaint_asap(); } })?; @@ -77,8 +78,9 @@ pub fn install_text_agent(runner_ref: &WebRunner) -> Result<(), JsValue> { &input, "compositionupdate", move |event: web_sys::CompositionEvent, runner: &mut AppRunner| { - if let Some(event) = event.data().map(egui::Event::CompositionUpdate) { - runner.input.raw.events.push(event); + if let Some(text) = event.data() { + let egui_event = egui::Event::Ime(egui::ImeEvent::Preedit(text)); + runner.input.raw.events.push(egui_event); runner.needs_repaint.repaint_asap(); } }, @@ -91,8 +93,9 @@ pub fn install_text_agent(runner_ref: &WebRunner) -> Result<(), JsValue> { is_composing.set(false); input_clone.set_value(""); - if let Some(event) = event.data().map(egui::Event::CompositionEnd) { - runner.input.raw.events.push(event); + if let Some(text) = event.data() { + let egui_event = egui::Event::Ime(egui::ImeEvent::Commit(text)); + runner.input.raw.events.push(egui_event); runner.needs_repaint.repaint_asap(); } } diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 1bba39b3cc6..81840e951a7 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -95,7 +95,7 @@ pub struct State { pointer_touch_id: Option, /// track ime state - input_method_editor_started: bool, + has_sent_ime_enabled: bool, #[cfg(feature = "accesskit")] accesskit: Option, @@ -136,7 +136,7 @@ impl State { simulate_touch_screen: false, pointer_touch_id: None, - input_method_editor_started: false, + has_sent_ime_enabled: false, #[cfg(feature = "accesskit")] accesskit: None, @@ -342,23 +342,39 @@ impl State { // We use input_method_editor_started to manually insert CompositionStart // between Commits. match ime { - winit::event::Ime::Enabled | winit::event::Ime::Disabled => (), - winit::event::Ime::Commit(text) => { - self.input_method_editor_started = false; + winit::event::Ime::Enabled => { self.egui_input .events - .push(egui::Event::CompositionEnd(text.clone())); + .push(egui::Event::Ime(egui::ImeEvent::Enabled)); + self.has_sent_ime_enabled = true; } - winit::event::Ime::Preedit(text, Some(_)) => { - if !self.input_method_editor_started { - self.input_method_editor_started = true; - self.egui_input.events.push(egui::Event::CompositionStart); + winit::event::Ime::Preedit(_, None) => {} + winit::event::Ime::Preedit(text, Some(_cursor)) => { + if !self.has_sent_ime_enabled { + self.egui_input + .events + .push(egui::Event::Ime(egui::ImeEvent::Enabled)); + self.has_sent_ime_enabled = true; } self.egui_input .events - .push(egui::Event::CompositionUpdate(text.clone())); + .push(egui::Event::Ime(egui::ImeEvent::Preedit(text.clone()))); + } + winit::event::Ime::Commit(text) => { + self.egui_input + .events + .push(egui::Event::Ime(egui::ImeEvent::Commit(text.clone()))); + self.egui_input + .events + .push(egui::Event::Ime(egui::ImeEvent::Disabled)); + self.has_sent_ime_enabled = false; + } + winit::event::Ime::Disabled => { + self.egui_input + .events + .push(egui::Event::Ime(egui::ImeEvent::Disabled)); + self.has_sent_ime_enabled = false; } - winit::event::Ime::Preedit(_, None) => {} }; EventResponse { @@ -601,7 +617,8 @@ impl State { }); // If we're not yet translating a touch or we're translating this very // touch … - if self.pointer_touch_id.is_none() || self.pointer_touch_id.unwrap() == touch.id { + if self.pointer_touch_id.is_none() || self.pointer_touch_id.unwrap_or_default() == touch.id + { // … emit PointerButton resp. PointerMoved events to emulate mouse match touch.phase { winit::event::TouchPhase::Started => { @@ -1531,7 +1548,7 @@ pub fn create_winit_window_builder( // We set sizes and positions in egui:s own ui points, which depends on the egui // zoom_factor and the native pixels per point, so we need to know that here. // We don't know what monitor the window will appear on though, but - // we'll try to fix that after the window is created in the vall to `apply_viewport_builder_to_window`. + // we'll try to fix that after the window is created in the call to `apply_viewport_builder_to_window`. let native_pixels_per_point = event_loop .primary_monitor() .or_else(|| event_loop.available_monitors().next()) diff --git a/crates/egui/src/data/input.rs b/crates/egui/src/data/input.rs index f61b9312f89..8217f4f5a4d 100644 --- a/crates/egui/src/data/input.rs +++ b/crates/egui/src/data/input.rs @@ -445,14 +445,8 @@ pub enum Event { /// * `zoom > 1`: pinch spread Zoom(f32), - /// IME composition start. - CompositionStart, - - /// A new IME candidate is being suggested. - CompositionUpdate(String), - - /// IME composition ended with this final result. - CompositionEnd(String), + /// IME Event + Ime(ImeEvent), /// On touch screens, report this *in addition to* /// [`Self::PointerMoved`], [`Self::PointerButton`], [`Self::PointerGone`] @@ -507,6 +501,25 @@ pub enum Event { }, } +/// IME event. +/// +/// See +#[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub enum ImeEvent { + /// Notifies when the IME was enabled. + Enabled, + + /// A new IME candidate is being suggested. + Preedit(String), + + /// IME composition ended with this final result. + Commit(String), + + /// Notifies when the IME was disabled. + Disabled, +} + /// Mouse button (or similar for touch input) #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index f022e860daf..c0b8532d6d9 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -950,47 +950,51 @@ fn events( .. } => check_for_mutating_key_press(os, &mut cursor_range, text, galley, modifiers, *key), - Event::CompositionStart => { - state.has_ime = true; - None - } - - Event::CompositionUpdate(text_mark) => { - // empty prediction can be produced when user press backspace - // or escape during ime. We should clear current text. - if text_mark != "\n" && text_mark != "\r" && state.has_ime { - let mut ccursor = text.delete_selected(&cursor_range); - let start_cursor = ccursor; - if !text_mark.is_empty() { - text.insert_text_at(&mut ccursor, text_mark, char_limit); - } + Event::Ime(ime_event) => match ime_event { + ImeEvent::Enabled => { + state.ime_enabled = true; state.ime_cursor_range = cursor_range; - Some(CCursorRange::two(start_cursor, ccursor)) - } else { None } - } - - Event::CompositionEnd(prediction) => { - // CompositionEnd only characters may be typed into TextEdit without trigger CompositionStart first, - // so do not check `state.has_ime = true` in the following statement. - if prediction != "\n" && prediction != "\r" { - state.has_ime = false; - let mut ccursor; - if !prediction.is_empty() - && cursor_range.secondary.ccursor.index - == state.ime_cursor_range.secondary.ccursor.index - { - ccursor = text.delete_selected(&cursor_range); - text.insert_text_at(&mut ccursor, prediction, char_limit); + ImeEvent::Preedit(text_mark) => { + if text_mark == "\n" || text_mark == "\r" { + None + } else { + // Empty prediction can be produced when user press backspace + // or escape during IME, so we clear current text. + let mut ccursor = text.delete_selected(&cursor_range); + let start_cursor = ccursor; + if !text_mark.is_empty() { + text.insert_text_at(&mut ccursor, text_mark, char_limit); + } + state.ime_cursor_range = cursor_range; + Some(CCursorRange::two(start_cursor, ccursor)) + } + } + ImeEvent::Commit(prediction) => { + if prediction == "\n" || prediction == "\r" { + None } else { - ccursor = cursor_range.primary.ccursor; + state.ime_enabled = false; + + if !prediction.is_empty() + && cursor_range.secondary.ccursor.index + == state.ime_cursor_range.secondary.ccursor.index + { + let mut ccursor = text.delete_selected(&cursor_range); + text.insert_text_at(&mut ccursor, prediction, char_limit); + Some(CCursorRange::one(ccursor)) + } else { + let ccursor = cursor_range.primary.ccursor; + Some(CCursorRange::one(ccursor)) + } } - Some(CCursorRange::one(ccursor)) - } else { + } + ImeEvent::Disabled => { + state.ime_enabled = false; None } - } + }, _ => None, }; diff --git a/crates/egui/src/widgets/text_edit/state.rs b/crates/egui/src/widgets/text_edit/state.rs index d0334da3213..ef4a8909a73 100644 --- a/crates/egui/src/widgets/text_edit/state.rs +++ b/crates/egui/src/widgets/text_edit/state.rs @@ -42,7 +42,7 @@ pub struct TextEditState { // If IME candidate window is shown on this text edit. #[cfg_attr(feature = "serde", serde(skip))] - pub(crate) has_ime: bool, + pub(crate) ime_enabled: bool, // cursor range for IME candidate. #[cfg_attr(feature = "serde", serde(skip))] From ff8cfc2aa029b73d400a406f11f6923040da07e6 Mon Sep 17 00:00:00 2001 From: lopo <59609929+lopo12123@users.noreply.github.com> Date: Mon, 22 Apr 2024 18:44:21 +0800 Subject: [PATCH 107/134] Allow users to create viewports larger than monitor on Windows & macOS (#4337) Added clamp_size_to_monitor_size field on ViewportBuilder, which means whether clamp the window's size to monitor's size. (default to `true`) * Closes https://github.com/emilk/egui/issues/3389 ### simple example ```rust pub struct MyApp {} impl MyApp { pub fn new() -> MyApp { MyApp {} } } impl eframe::App for MyApp { fn update(&mut self, ctx: &Context, frame: &mut eframe::Frame) { egui::CentralPanel::default() .frame(Frame::none().fill(Color32::DARK_GRAY)) .show(ctx, |ui| { if ctx.input(|i| i.key_pressed(Key::Escape)) { ctx.send_viewport_cmd(ViewportCommand::Close); } }); } } pub fn main() { let option = eframe::NativeOptions { viewport: ViewportBuilder::default() .with_position([10.0, 10.0]) .with_inner_size([3000.0, 2000.0]) .with_clamp_size_to_monitor_size(false), ..Default::default() }; eframe::run_native( "a large window app", option, Box::new(|ctx| Box::new(MyApp::new())), ).unwrap(); } ``` It works on my windows (with 3 monitors), but I don't have a test environment for macos --------- Co-authored-by: Emil Ernerfeldt --- crates/eframe/src/native/epi_integration.rs | 23 +++++++++++++++------ crates/egui-winit/src/lib.rs | 1 + crates/egui/src/viewport.rs | 22 ++++++++++++++++++++ 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/crates/eframe/src/native/epi_integration.rs b/crates/eframe/src/native/epi_integration.rs index fbf7b6dc078..b09f0f0e0c7 100644 --- a/crates/eframe/src/native/epi_integration.rs +++ b/crates/eframe/src/native/epi_integration.rs @@ -20,14 +20,23 @@ pub fn viewport_builder( let mut viewport_builder = native_options.viewport.clone(); + let clamp_size_to_monitor_size = viewport_builder.clamp_size_to_monitor_size.unwrap_or( + // On some Linux systems, a window size larger than the monitor causes crashes + cfg!(target_os = "linux"), + ); + // Always use the default window size / position on iOS. Trying to restore the previous position // causes the window to be shown too small. #[cfg(not(target_os = "ios"))] let inner_size_points = if let Some(mut window_settings) = window_settings { // Restore pos/size from previous session - window_settings - .clamp_size_to_sane_values(largest_monitor_point_size(egui_zoom_factor, event_loop)); + if clamp_size_to_monitor_size { + window_settings.clamp_size_to_sane_values(largest_monitor_point_size( + egui_zoom_factor, + event_loop, + )); + } window_settings.clamp_position_to_monitors(egui_zoom_factor, event_loop); viewport_builder = window_settings.initialize_viewport_builder(viewport_builder); @@ -37,10 +46,12 @@ pub fn viewport_builder( viewport_builder = viewport_builder.with_position(pos); } - if let Some(initial_window_size) = viewport_builder.inner_size { - let initial_window_size = initial_window_size - .at_most(largest_monitor_point_size(egui_zoom_factor, event_loop)); - viewport_builder = viewport_builder.with_inner_size(initial_window_size); + if clamp_size_to_monitor_size { + if let Some(initial_window_size) = viewport_builder.inner_size { + let initial_window_size = initial_window_size + .at_most(largest_monitor_point_size(egui_zoom_factor, event_loop)); + viewport_builder = viewport_builder.with_inner_size(initial_window_size); + } } viewport_builder.inner_size diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 81840e951a7..313cc58906b 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -1598,6 +1598,7 @@ pub fn create_winit_window_builder( window_type: _window_type, mouse_passthrough: _, // handled in `apply_viewport_builder_to_window` + clamp_size_to_monitor_size: _, // Handled in `viewport_builder` in `epi_integration.rs` } = viewport_builder; let mut window_builder = winit::window::WindowBuilder::new() diff --git a/crates/egui/src/viewport.rs b/crates/egui/src/viewport.rs index ad7332877c0..090e93b3fe5 100644 --- a/crates/egui/src/viewport.rs +++ b/crates/egui/src/viewport.rs @@ -275,6 +275,11 @@ pub struct ViewportBuilder { pub min_inner_size: Option, pub max_inner_size: Option, + /// Whether clamp the window's size to monitor's size. The default is `true` on linux, otherwise it is `false`. + /// + /// Note: On some Linux systems, a window size larger than the monitor causes crashes + pub clamp_size_to_monitor_size: Option, + pub fullscreen: Option, pub maximized: Option, pub resizable: Option, @@ -493,6 +498,15 @@ impl ViewportBuilder { self } + /// Sets whether clamp the window's size to monitor's size. The default is `true` on linux, otherwise it is `false`. + /// + /// Note: On some Linux systems, a window size larger than the monitor causes crashes + #[inline] + pub fn with_clamp_size_to_monitor_size(mut self, value: bool) -> Self { + self.clamp_size_to_monitor_size = Some(value); + self + } + /// Does not work on X11. #[inline] pub fn with_close_button(mut self, value: bool) -> Self { @@ -606,6 +620,7 @@ impl ViewportBuilder { inner_size: new_inner_size, min_inner_size: new_min_inner_size, max_inner_size: new_max_inner_size, + clamp_size_to_monitor_size: new_clamp_size_to_monitor_size, fullscreen: new_fullscreen, maximized: new_maximized, resizable: new_resizable, @@ -740,6 +755,13 @@ impl ViewportBuilder { let mut recreate_window = false; + if new_clamp_size_to_monitor_size.is_some() + && self.clamp_size_to_monitor_size != new_clamp_size_to_monitor_size + { + self.clamp_size_to_monitor_size = new_clamp_size_to_monitor_size; + recreate_window = true; + } + if new_active.is_some() && self.active != new_active { self.active = new_active; recreate_window = true; From 2ce82cce2128a260daacf622e955a43a2ed67999 Mon Sep 17 00:00:00 2001 From: Joe Sorensen Date: Mon, 22 Apr 2024 10:35:09 -0600 Subject: [PATCH 108/134] Added ability to define colors at UV coordinates along a path (#4353) I had to make a couple types not Copy because closures, but it should'nt be a massive deal. I tried my best to make the API change as non breaking as possible. Anywhere a PathStroke is used, you can just use a normal Stroke instead. As mentioned above, the bezier paths couldn't be copy anymore, but IMO that's a minor caveat. --------- Co-authored-by: Emil Ernerfeldt --- crates/ecolor/Cargo.toml | 1 - crates/egui/src/painter.rs | 16 +- crates/egui_demo_lib/Cargo.toml | 2 +- .../egui_demo_lib/src/demo/dancing_strings.rs | 28 ++- crates/epaint/benches/benchmark.rs | 160 +++++++++++++- crates/epaint/src/bezier.rs | 28 +-- crates/epaint/src/color.rs | 48 +++++ crates/epaint/src/lib.rs | 4 +- crates/epaint/src/shape.rs | 26 ++- crates/epaint/src/shape_transform.rs | 69 ++++-- crates/epaint/src/stroke.rs | 67 ++++++ crates/epaint/src/tessellator.rs | 201 ++++++++++++++---- crates/epaint/src/text/text_layout.rs | 4 +- 13 files changed, 550 insertions(+), 104 deletions(-) create mode 100644 crates/epaint/src/color.rs diff --git a/crates/ecolor/Cargo.toml b/crates/ecolor/Cargo.toml index 4cdbea9124b..611b9a4dec9 100644 --- a/crates/ecolor/Cargo.toml +++ b/crates/ecolor/Cargo.toml @@ -30,7 +30,6 @@ extra_debug_asserts = [] ## Always enable additional checks. extra_asserts = [] - [dependencies] #! ### Optional dependencies diff --git a/crates/egui/src/painter.rs b/crates/egui/src/painter.rs index 788319dc936..0935587cc7b 100644 --- a/crates/egui/src/painter.rs +++ b/crates/egui/src/painter.rs @@ -7,7 +7,7 @@ use crate::{ }; use epaint::{ text::{Fonts, Galley, LayoutJob}, - CircleShape, ClippedShape, RectShape, Rounding, Shape, Stroke, + CircleShape, ClippedShape, PathStroke, RectShape, Rounding, Shape, Stroke, }; /// Helper to paint shapes and text to a specific region on a specific layer. @@ -280,7 +280,7 @@ impl Painter { /// # Paint different primitives impl Painter { /// Paints a line from the first point to the second. - pub fn line_segment(&self, points: [Pos2; 2], stroke: impl Into) -> ShapeIdx { + pub fn line_segment(&self, points: [Pos2; 2], stroke: impl Into) -> ShapeIdx { self.add(Shape::LineSegment { points, stroke: stroke.into(), @@ -288,13 +288,13 @@ impl Painter { } /// Paints a horizontal line. - pub fn hline(&self, x: impl Into, y: f32, stroke: impl Into) -> ShapeIdx { - self.add(Shape::hline(x, y, stroke)) + pub fn hline(&self, x: impl Into, y: f32, stroke: impl Into) -> ShapeIdx { + self.add(Shape::hline(x, y, stroke.into())) } /// Paints a vertical line. - pub fn vline(&self, x: f32, y: impl Into, stroke: impl Into) -> ShapeIdx { - self.add(Shape::vline(x, y, stroke)) + pub fn vline(&self, x: f32, y: impl Into, stroke: impl Into) -> ShapeIdx { + self.add(Shape::vline(x, y, stroke.into())) } pub fn circle( @@ -513,7 +513,7 @@ impl Painter { } fn tint_shape_towards(shape: &mut Shape, target: Color32) { - epaint::shape_transform::adjust_colors(shape, &|color| { + epaint::shape_transform::adjust_colors(shape, move |color| { if *color != Color32::PLACEHOLDER { *color = crate::ecolor::tint_color_towards(*color, target); } @@ -521,7 +521,7 @@ fn tint_shape_towards(shape: &mut Shape, target: Color32) { } fn multiply_opacity(shape: &mut Shape, opacity: f32) { - epaint::shape_transform::adjust_colors(shape, &|color| { + epaint::shape_transform::adjust_colors(shape, move |color| { if *color != Color32::PLACEHOLDER { *color = color.gamma_multiply(opacity); } diff --git a/crates/egui_demo_lib/Cargo.toml b/crates/egui_demo_lib/Cargo.toml index a08a88fe8d0..8c5a703b5d6 100644 --- a/crates/egui_demo_lib/Cargo.toml +++ b/crates/egui_demo_lib/Cargo.toml @@ -38,7 +38,7 @@ syntect = ["egui_extras/syntect"] [dependencies] -egui = { workspace = true, default-features = false } +egui = { workspace = true, default-features = false, features = ["color-hex"] } egui_extras = { workspace = true, features = ["default"] } egui_plot = { workspace = true, features = ["default"] } diff --git a/crates/egui_demo_lib/src/demo/dancing_strings.rs b/crates/egui_demo_lib/src/demo/dancing_strings.rs index 3beb323e75d..a2b560ee723 100644 --- a/crates/egui_demo_lib/src/demo/dancing_strings.rs +++ b/crates/egui_demo_lib/src/demo/dancing_strings.rs @@ -1,9 +1,11 @@ -use egui::{containers::*, *}; +use egui::{containers::*, epaint::PathStroke, *}; #[derive(Default)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", serde(default))] -pub struct DancingStrings {} +pub struct DancingStrings { + colors: bool, +} impl super::Demo for DancingStrings { fn name(&self) -> &'static str { @@ -28,6 +30,9 @@ impl super::View for DancingStrings { Color32::from_black_alpha(240) }; + ui.checkbox(&mut self.colors, "Colored") + .on_hover_text("Demonstrates how a path can have varying color across its length."); + Frame::canvas(ui.style()).show(ui, |ui| { ui.ctx().request_repaint(); let time = ui.input(|i| i.time); @@ -55,7 +60,24 @@ impl super::View for DancingStrings { .collect(); let thickness = 10.0 / mode as f32; - shapes.push(epaint::Shape::line(points, Stroke::new(thickness, color))); + shapes.push(epaint::Shape::line( + points, + if self.colors { + PathStroke::new_uv(thickness, move |rect, p| { + let t = remap(p.x, rect.x_range(), -1.0..=1.0).abs(); + let center_color = hex_color!("#5BCEFA"); + let outer_color = hex_color!("#F5A9B8"); + + Color32::from_rgb( + lerp(center_color.r() as f32..=outer_color.r() as f32, t) as u8, + lerp(center_color.g() as f32..=outer_color.g() as f32, t) as u8, + lerp(center_color.b() as f32..=outer_color.b() as f32, t) as u8, + ) + }) + } else { + PathStroke::new(thickness, color) + }, + )); } ui.painter().extend(shapes); diff --git a/crates/epaint/benches/benchmark.rs b/crates/epaint/benches/benchmark.rs index 709adbfae9c..6323137fa50 100644 --- a/crates/epaint/benches/benchmark.rs +++ b/crates/epaint/benches/benchmark.rs @@ -1,6 +1,6 @@ use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use epaint::*; +use epaint::{tessellator::Path, *}; fn single_dashed_lines(c: &mut Criterion) { c.bench_function("single_dashed_lines", move |b| { @@ -72,10 +72,166 @@ fn tessellate_circles(c: &mut Criterion) { }); } +fn thick_line_solid(c: &mut Criterion) { + c.bench_function("thick_solid_line", move |b| { + let line = [pos2(0.0, 0.0), pos2(50.0, 0.0), pos2(100.0, 1.0)]; + let mut path = Path::default(); + path.add_open_points(&line); + + b.iter(|| { + let mut mesh = Mesh::default(); + path.stroke_closed(1.5, &Stroke::new(2.0, Color32::RED).into(), &mut mesh); + + black_box(mesh); + }); + }); +} + +fn thick_large_line_solid(c: &mut Criterion) { + c.bench_function("thick_large_solid_line", move |b| { + let line = (0..1000).map(|i| pos2(i as f32, 10.0)).collect::>(); + let mut path = Path::default(); + path.add_open_points(&line); + + b.iter(|| { + let mut mesh = Mesh::default(); + path.stroke_closed(1.5, &Stroke::new(2.0, Color32::RED).into(), &mut mesh); + + black_box(mesh); + }); + }); +} + +fn thin_line_solid(c: &mut Criterion) { + c.bench_function("thin_solid_line", move |b| { + let line = [pos2(0.0, 0.0), pos2(50.0, 0.0), pos2(100.0, 1.0)]; + let mut path = Path::default(); + path.add_open_points(&line); + + b.iter(|| { + let mut mesh = Mesh::default(); + path.stroke_closed(1.5, &Stroke::new(0.5, Color32::RED).into(), &mut mesh); + + black_box(mesh); + }); + }); +} + +fn thin_large_line_solid(c: &mut Criterion) { + c.bench_function("thin_large_solid_line", move |b| { + let line = (0..1000).map(|i| pos2(i as f32, 10.0)).collect::>(); + let mut path = Path::default(); + path.add_open_points(&line); + + b.iter(|| { + let mut mesh = Mesh::default(); + path.stroke_closed(1.5, &Stroke::new(0.5, Color32::RED).into(), &mut mesh); + + black_box(mesh); + }); + }); +} + +fn thick_line_uv(c: &mut Criterion) { + c.bench_function("thick_uv_line", move |b| { + let line = [pos2(0.0, 0.0), pos2(50.0, 0.0), pos2(100.0, 1.0)]; + let mut path = Path::default(); + path.add_open_points(&line); + + b.iter(|| { + let mut mesh = Mesh::default(); + path.stroke_closed( + 1.5, + &PathStroke::new_uv(2.0, |_, p| { + black_box(p * 2.0); + Color32::RED + }), + &mut mesh, + ); + + black_box(mesh); + }); + }); +} + +fn thick_large_line_uv(c: &mut Criterion) { + c.bench_function("thick_large_uv_line", move |b| { + let line = (0..1000).map(|i| pos2(i as f32, 10.0)).collect::>(); + let mut path = Path::default(); + path.add_open_points(&line); + + b.iter(|| { + let mut mesh = Mesh::default(); + path.stroke_closed( + 1.5, + &PathStroke::new_uv(2.0, |_, p| { + black_box(p * 2.0); + Color32::RED + }), + &mut mesh, + ); + + black_box(mesh); + }); + }); +} + +fn thin_line_uv(c: &mut Criterion) { + c.bench_function("thin_uv_line", move |b| { + let line = [pos2(0.0, 0.0), pos2(50.0, 0.0), pos2(100.0, 1.0)]; + let mut path = Path::default(); + path.add_open_points(&line); + + b.iter(|| { + let mut mesh = Mesh::default(); + path.stroke_closed( + 1.5, + &PathStroke::new_uv(2.0, |_, p| { + black_box(p * 2.0); + Color32::RED + }), + &mut mesh, + ); + + black_box(mesh); + }); + }); +} + +fn thin_large_line_uv(c: &mut Criterion) { + c.bench_function("thin_large_uv_line", move |b| { + let line = (0..1000).map(|i| pos2(i as f32, 10.0)).collect::>(); + let mut path = Path::default(); + path.add_open_points(&line); + + b.iter(|| { + let mut mesh = Mesh::default(); + path.stroke_closed( + 1.5, + &PathStroke::new_uv(2.0, |_, p| { + black_box(p * 2.0); + Color32::RED + }), + &mut mesh, + ); + + black_box(mesh); + }); + }); +} + criterion_group!( benches, single_dashed_lines, many_dashed_lines, - tessellate_circles + tessellate_circles, + thick_line_solid, + thick_large_line_solid, + thin_line_solid, + thin_large_line_solid, + thick_line_uv, + thick_large_line_uv, + thin_line_uv, + thin_large_line_uv ); criterion_main!(benches); diff --git a/crates/epaint/src/bezier.rs b/crates/epaint/src/bezier.rs index 3da99f33b65..4ad9a28228f 100644 --- a/crates/epaint/src/bezier.rs +++ b/crates/epaint/src/bezier.rs @@ -3,7 +3,7 @@ use std::ops::Range; -use crate::{shape::Shape, Color32, PathShape, Stroke}; +use crate::{shape::Shape, Color32, PathShape, PathStroke}; use emath::*; // ---------------------------------------------------------------------------- @@ -11,7 +11,7 @@ use emath::*; /// A cubic [Bézier Curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve). /// /// See also [`QuadraticBezierShape`]. -#[derive(Copy, Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct CubicBezierShape { /// The first point is the starting point and the last one is the ending point of the curve. @@ -20,7 +20,7 @@ pub struct CubicBezierShape { pub closed: bool, pub fill: Color32, - pub stroke: Stroke, + pub stroke: PathStroke, } impl CubicBezierShape { @@ -32,7 +32,7 @@ impl CubicBezierShape { points: [Pos2; 4], closed: bool, fill: Color32, - stroke: impl Into, + stroke: impl Into, ) -> Self { Self { points, @@ -52,7 +52,7 @@ impl CubicBezierShape { points, closed: self.closed, fill: self.fill, - stroke: self.stroke, + stroke: self.stroke.clone(), } } @@ -69,7 +69,7 @@ impl CubicBezierShape { points, closed: self.closed, fill: self.fill, - stroke: self.stroke, + stroke: self.stroke.clone(), }; pathshapes.push(pathshape); } @@ -156,7 +156,7 @@ impl CubicBezierShape { points: [d_from, d_ctrl, d_to], closed: self.closed, fill: self.fill, - stroke: self.stroke, + stroke: self.stroke.clone(), }; let delta_t = t_range.end - t_range.start; let q_start = q.sample(t_range.start); @@ -168,7 +168,7 @@ impl CubicBezierShape { points: [from, ctrl1, ctrl2, to], closed: self.closed, fill: self.fill, - stroke: self.stroke, + stroke: self.stroke.clone(), } } @@ -375,7 +375,7 @@ impl From for Shape { /// A quadratic [Bézier Curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve). /// /// See also [`CubicBezierShape`]. -#[derive(Copy, Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct QuadraticBezierShape { /// The first point is the starting point and the last one is the ending point of the curve. @@ -384,7 +384,7 @@ pub struct QuadraticBezierShape { pub closed: bool, pub fill: Color32, - pub stroke: Stroke, + pub stroke: PathStroke, } impl QuadraticBezierShape { @@ -397,7 +397,7 @@ impl QuadraticBezierShape { points: [Pos2; 3], closed: bool, fill: Color32, - stroke: impl Into, + stroke: impl Into, ) -> Self { Self { points, @@ -417,7 +417,7 @@ impl QuadraticBezierShape { points, closed: self.closed, fill: self.fill, - stroke: self.stroke, + stroke: self.stroke.clone(), } } @@ -429,7 +429,7 @@ impl QuadraticBezierShape { points, closed: self.closed, fill: self.fill, - stroke: self.stroke, + stroke: self.stroke.clone(), } } @@ -688,7 +688,7 @@ fn single_curve_approximation(curve: &CubicBezierShape) -> QuadraticBezierShape points: [curve.points[0], c, curve.points[3]], closed: curve.closed, fill: curve.fill, - stroke: curve.stroke, + stroke: curve.stroke.clone(), } } diff --git a/crates/epaint/src/color.rs b/crates/epaint/src/color.rs new file mode 100644 index 00000000000..54106c10d3f --- /dev/null +++ b/crates/epaint/src/color.rs @@ -0,0 +1,48 @@ +use std::{fmt::Debug, sync::Arc}; + +use ecolor::Color32; +use emath::{Pos2, Rect}; + +/// How paths will be colored. +#[derive(Clone)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub enum ColorMode { + /// The entire path is one solid color, this is the default. + Solid(Color32), + + /// Provide a callback which takes in the path's bounding box and a position and converts it to a color. + /// When used with a path, the bounding box will have a margin of [`TessellationOptions::feathering_size_in_pixels`](`crate::tessellator::TessellationOptions::feathering_size_in_pixels`) + /// + /// **This cannot be serialized** + #[cfg_attr(feature = "serde", serde(skip))] + UV(Arc Color32 + Send + Sync>), +} + +impl Default for ColorMode { + fn default() -> Self { + Self::Solid(Color32::TRANSPARENT) + } +} + +impl Debug for ColorMode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Solid(arg0) => f.debug_tuple("Solid").field(arg0).finish(), + Self::UV(_arg0) => f.debug_tuple("UV").field(&"").finish(), + } + } +} + +impl PartialEq for ColorMode { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Solid(l0), Self::Solid(r0)) => l0 == r0, + (Self::UV(_l0), Self::UV(_r0)) => false, + _ => false, + } + } +} + +impl ColorMode { + pub const TRANSPARENT: Self = Self::Solid(Color32::TRANSPARENT); +} diff --git a/crates/epaint/src/lib.rs b/crates/epaint/src/lib.rs index f7ee04cb75d..c8c47bdf9b2 100644 --- a/crates/epaint/src/lib.rs +++ b/crates/epaint/src/lib.rs @@ -26,6 +26,7 @@ #![cfg_attr(not(feature = "puffin"), forbid(unsafe_code))] mod bezier; +pub mod color; pub mod image; mod margin; mod mesh; @@ -44,6 +45,7 @@ pub mod util; pub use self::{ bezier::{CubicBezierShape, QuadraticBezierShape}, + color::ColorMode, image::{ColorImage, FontImage, ImageData, ImageDelta}, margin::Margin, mesh::{Mesh, Mesh16, Vertex}, @@ -53,7 +55,7 @@ pub use self::{ Rounding, Shape, TextShape, }, stats::PaintStats, - stroke::Stroke, + stroke::{PathStroke, Stroke}, tessellator::{TessellationOptions, Tessellator}, text::{FontFamily, FontId, Fonts, Galley}, texture_atlas::TextureAtlas, diff --git a/crates/epaint/src/shape.rs b/crates/epaint/src/shape.rs index 7922a92d6d5..336c9087316 100644 --- a/crates/epaint/src/shape.rs +++ b/crates/epaint/src/shape.rs @@ -3,6 +3,7 @@ use std::{any::Any, sync::Arc}; use crate::{ + stroke::PathStroke, text::{FontId, Fonts, Galley}, Color32, Mesh, Stroke, TextureId, }; @@ -34,7 +35,10 @@ pub enum Shape { Ellipse(EllipseShape), /// A line between two points. - LineSegment { points: [Pos2; 2], stroke: Stroke }, + LineSegment { + points: [Pos2; 2], + stroke: PathStroke, + }, /// A series of lines between points. /// The path can have a stroke and/or fill (if closed). @@ -88,7 +92,7 @@ impl Shape { /// A line between two points. /// More efficient than calling [`Self::line`]. #[inline] - pub fn line_segment(points: [Pos2; 2], stroke: impl Into) -> Self { + pub fn line_segment(points: [Pos2; 2], stroke: impl Into) -> Self { Self::LineSegment { points, stroke: stroke.into(), @@ -96,7 +100,7 @@ impl Shape { } /// A horizontal line. - pub fn hline(x: impl Into, y: f32, stroke: impl Into) -> Self { + pub fn hline(x: impl Into, y: f32, stroke: impl Into) -> Self { let x = x.into(); Self::LineSegment { points: [pos2(x.min, y), pos2(x.max, y)], @@ -105,7 +109,7 @@ impl Shape { } /// A vertical line. - pub fn vline(x: f32, y: impl Into, stroke: impl Into) -> Self { + pub fn vline(x: f32, y: impl Into, stroke: impl Into) -> Self { let y = y.into(); Self::LineSegment { points: [pos2(x, y.min), pos2(x, y.max)], @@ -117,13 +121,13 @@ impl Shape { /// /// Use [`Self::line_segment`] instead if your line only connects two points. #[inline] - pub fn line(points: Vec, stroke: impl Into) -> Self { + pub fn line(points: Vec, stroke: impl Into) -> Self { Self::Path(PathShape::line(points, stroke)) } /// A line that closes back to the start point again. #[inline] - pub fn closed_line(points: Vec, stroke: impl Into) -> Self { + pub fn closed_line(points: Vec, stroke: impl Into) -> Self { Self::Path(PathShape::closed_line(points, stroke)) } @@ -224,7 +228,7 @@ impl Shape { pub fn convex_polygon( points: Vec, fill: impl Into, - stroke: impl Into, + stroke: impl Into, ) -> Self { Self::Path(PathShape::convex_polygon(points, fill, stroke)) } @@ -586,7 +590,7 @@ pub struct PathShape { pub fill: Color32, /// Color and thickness of the line. - pub stroke: Stroke, + pub stroke: PathStroke, // TODO(emilk): Add texture support either by supplying uv for each point, // or by some transform from points to uv (e.g. a callback or a linear transform matrix). } @@ -596,7 +600,7 @@ impl PathShape { /// /// Use [`Shape::line_segment`] instead if your line only connects two points. #[inline] - pub fn line(points: Vec, stroke: impl Into) -> Self { + pub fn line(points: Vec, stroke: impl Into) -> Self { Self { points, closed: false, @@ -607,7 +611,7 @@ impl PathShape { /// A line that closes back to the start point again. #[inline] - pub fn closed_line(points: Vec, stroke: impl Into) -> Self { + pub fn closed_line(points: Vec, stroke: impl Into) -> Self { Self { points, closed: true, @@ -623,7 +627,7 @@ impl PathShape { pub fn convex_polygon( points: Vec, fill: impl Into, - stroke: impl Into, + stroke: impl Into, ) -> Self { Self { points, diff --git a/crates/epaint/src/shape_transform.rs b/crates/epaint/src/shape_transform.rs index 8ff65d2a045..d0ab91536d1 100644 --- a/crates/epaint/src/shape_transform.rs +++ b/crates/epaint/src/shape_transform.rs @@ -1,7 +1,12 @@ +use std::sync::Arc; + use crate::*; /// Remember to handle [`Color32::PLACEHOLDER`] specially! -pub fn adjust_colors(shape: &mut Shape, adjust_color: &impl Fn(&mut Color32)) { +pub fn adjust_colors( + shape: &mut Shape, + adjust_color: impl Fn(&mut Color32) + Send + Sync + Copy + 'static, +) { #![allow(clippy::match_same_arms)] match shape { Shape::Noop => {} @@ -10,8 +15,48 @@ pub fn adjust_colors(shape: &mut Shape, adjust_color: &impl Fn(&mut Color32)) { adjust_colors(shape, adjust_color); } } - Shape::LineSegment { stroke, points: _ } => { - adjust_color(&mut stroke.color); + Shape::LineSegment { stroke, points: _ } => match &stroke.color { + color::ColorMode::Solid(mut col) => adjust_color(&mut col), + color::ColorMode::UV(callback) => { + let callback = callback.clone(); + stroke.color = color::ColorMode::UV(Arc::new(Box::new(move |rect, pos| { + let mut col = callback(rect, pos); + adjust_color(&mut col); + col + }))); + } + }, + + Shape::Path(PathShape { + points: _, + closed: _, + fill, + stroke, + }) + | Shape::QuadraticBezier(QuadraticBezierShape { + points: _, + closed: _, + fill, + stroke, + }) + | Shape::CubicBezier(CubicBezierShape { + points: _, + closed: _, + fill, + stroke, + }) => { + adjust_color(fill); + match &stroke.color { + color::ColorMode::Solid(mut col) => adjust_color(&mut col), + color::ColorMode::UV(callback) => { + let callback = callback.clone(); + stroke.color = color::ColorMode::UV(Arc::new(Box::new(move |rect, pos| { + let mut col = callback(rect, pos); + adjust_color(&mut col); + col + }))); + } + } } Shape::Circle(CircleShape { @@ -26,12 +71,6 @@ pub fn adjust_colors(shape: &mut Shape, adjust_color: &impl Fn(&mut Color32)) { fill, stroke, }) - | Shape::Path(PathShape { - points: _, - closed: _, - fill, - stroke, - }) | Shape::Rect(RectShape { rect: _, rounding: _, @@ -40,18 +79,6 @@ pub fn adjust_colors(shape: &mut Shape, adjust_color: &impl Fn(&mut Color32)) { blur_width: _, fill_texture_id: _, uv: _, - }) - | Shape::QuadraticBezier(QuadraticBezierShape { - points: _, - closed: _, - fill, - stroke, - }) - | Shape::CubicBezier(CubicBezierShape { - points: _, - closed: _, - fill, - stroke, }) => { adjust_color(fill); adjust_color(&mut stroke.color); diff --git a/crates/epaint/src/stroke.rs b/crates/epaint/src/stroke.rs index 9dafef31ddc..36ecac253d3 100644 --- a/crates/epaint/src/stroke.rs +++ b/crates/epaint/src/stroke.rs @@ -1,5 +1,7 @@ #![allow(clippy::derived_hash_with_manual_eq)] // We need to impl Hash for f32, but we don't implement Eq, which is fine +use std::{fmt::Debug, sync::Arc}; + use super::*; /// Describes the width and color of a line. @@ -52,3 +54,68 @@ impl std::hash::Hash for Stroke { color.hash(state); } } + +/// Describes the width and color of paths. The color can either be solid or provided by a callback. For more information, see [`ColorMode`] +/// +/// The default stroke is the same as [`Stroke::NONE`]. +#[derive(Clone, Debug, Default, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub struct PathStroke { + pub width: f32, + pub color: ColorMode, +} + +impl PathStroke { + /// Same as [`PathStroke::default`]. + pub const NONE: Self = Self { + width: 0.0, + color: ColorMode::TRANSPARENT, + }; + + #[inline] + pub fn new(width: impl Into, color: impl Into) -> Self { + Self { + width: width.into(), + color: ColorMode::Solid(color.into()), + } + } + + /// Create a new `PathStroke` with a UV function + /// + /// The bounding box passed to the callback will have a margin of [`TessellationOptions::feathering_size_in_pixels`](`crate::tessellator::TessellationOptions::feathering_size_in_pixels`) + #[inline] + pub fn new_uv( + width: impl Into, + callback: impl Fn(Rect, Pos2) -> Color32 + Send + Sync + 'static, + ) -> Self { + Self { + width: width.into(), + color: ColorMode::UV(Arc::new(callback)), + } + } + + /// True if width is zero or color is solid and transparent + #[inline] + pub fn is_empty(&self) -> bool { + self.width <= 0.0 || self.color == ColorMode::TRANSPARENT + } +} + +impl From<(f32, Color)> for PathStroke +where + Color: Into, +{ + #[inline(always)] + fn from((width, color): (f32, Color)) -> Self { + Self::new(width, color) + } +} + +impl From for PathStroke { + fn from(value: Stroke) -> Self { + Self { + width: value.width, + color: ColorMode::Solid(value.color), + } + } +} diff --git a/crates/epaint/src/tessellator.rs b/crates/epaint/src/tessellator.rs index f5b3d9e3cb1..c7dc31b75ab 100644 --- a/crates/epaint/src/tessellator.rs +++ b/crates/epaint/src/tessellator.rs @@ -9,6 +9,9 @@ use crate::texture_atlas::PreparedDisc; use crate::*; use emath::*; +use self::color::ColorMode; +use self::stroke::PathStroke; + // ---------------------------------------------------------------------------- #[allow(clippy::approx_constant)] @@ -471,16 +474,22 @@ impl Path { } /// Open-ended. - pub fn stroke_open(&self, feathering: f32, stroke: Stroke, out: &mut Mesh) { + pub fn stroke_open(&self, feathering: f32, stroke: &PathStroke, out: &mut Mesh) { stroke_path(feathering, &self.0, PathType::Open, stroke, out); } /// A closed path (returning to the first point). - pub fn stroke_closed(&self, feathering: f32, stroke: Stroke, out: &mut Mesh) { + pub fn stroke_closed(&self, feathering: f32, stroke: &PathStroke, out: &mut Mesh) { stroke_path(feathering, &self.0, PathType::Closed, stroke, out); } - pub fn stroke(&self, feathering: f32, path_type: PathType, stroke: Stroke, out: &mut Mesh) { + pub fn stroke( + &self, + feathering: f32, + path_type: PathType, + stroke: &PathStroke, + out: &mut Mesh, + ) { stroke_path(feathering, &self.0, path_type, stroke, out); } @@ -864,19 +873,28 @@ fn stroke_path( feathering: f32, path: &[PathPoint], path_type: PathType, - stroke: Stroke, + stroke: &PathStroke, out: &mut Mesh, ) { let n = path.len() as u32; - if stroke.width <= 0.0 || stroke.color == Color32::TRANSPARENT || n < 2 { + if stroke.width <= 0.0 || stroke.color == ColorMode::TRANSPARENT || n < 2 { return; } let idx = out.vertices.len() as u32; + // expand the bounding box to include the thickness of the path + let bbox = Rect::from_points(&path.iter().map(|p| p.pos).collect::>()) + .expand((stroke.width / 2.0) + feathering); + + let get_color = |col: &ColorMode, pos: Pos2| match col { + ColorMode::Solid(col) => *col, + ColorMode::UV(fun) => fun(bbox, pos), + }; + if feathering > 0.0 { - let color_inner = stroke.color; + let color_inner = &stroke.color; let color_outer = Color32::TRANSPARENT; let thin_line = stroke.width <= feathering; @@ -889,9 +907,11 @@ fn stroke_path( */ // Fade out as it gets thinner: - let color_inner = mul_color(color_inner, stroke.width / feathering); - if color_inner == Color32::TRANSPARENT { - return; + if let ColorMode::Solid(col) = color_inner { + let color_inner = mul_color(*col, stroke.width / feathering); + if color_inner == Color32::TRANSPARENT { + return; + } } out.reserve_triangles(4 * n as usize); @@ -904,7 +924,10 @@ fn stroke_path( let p = p1.pos; let n = p1.normal; out.colored_vertex(p + n * feathering, color_outer); - out.colored_vertex(p, color_inner); + out.colored_vertex( + p, + mul_color(get_color(color_inner, p), stroke.width / feathering), + ); out.colored_vertex(p - n * feathering, color_outer); if connect_with_previous { @@ -943,8 +966,14 @@ fn stroke_path( let p = p1.pos; let n = p1.normal; out.colored_vertex(p + n * outer_rad, color_outer); - out.colored_vertex(p + n * inner_rad, color_inner); - out.colored_vertex(p - n * inner_rad, color_inner); + out.colored_vertex( + p + n * inner_rad, + get_color(color_inner, p + n * inner_rad), + ); + out.colored_vertex( + p - n * inner_rad, + get_color(color_inner, p - n * inner_rad), + ); out.colored_vertex(p - n * outer_rad, color_outer); out.add_triangle(idx + 4 * i0 + 0, idx + 4 * i0 + 1, idx + 4 * i1 + 0); @@ -983,8 +1012,14 @@ fn stroke_path( let n = end.normal; let back_extrude = n.rot90() * feathering; out.colored_vertex(p + n * outer_rad + back_extrude, color_outer); - out.colored_vertex(p + n * inner_rad, color_inner); - out.colored_vertex(p - n * inner_rad, color_inner); + out.colored_vertex( + p + n * inner_rad, + get_color(color_inner, p + n * inner_rad), + ); + out.colored_vertex( + p - n * inner_rad, + get_color(color_inner, p - n * inner_rad), + ); out.colored_vertex(p - n * outer_rad + back_extrude, color_outer); out.add_triangle(idx + 0, idx + 1, idx + 2); @@ -997,8 +1032,14 @@ fn stroke_path( let p = point.pos; let n = point.normal; out.colored_vertex(p + n * outer_rad, color_outer); - out.colored_vertex(p + n * inner_rad, color_inner); - out.colored_vertex(p - n * inner_rad, color_inner); + out.colored_vertex( + p + n * inner_rad, + get_color(color_inner, p + n * inner_rad), + ); + out.colored_vertex( + p - n * inner_rad, + get_color(color_inner, p - n * inner_rad), + ); out.colored_vertex(p - n * outer_rad, color_outer); out.add_triangle(idx + 4 * i0 + 0, idx + 4 * i0 + 1, idx + 4 * i1 + 0); @@ -1020,8 +1061,14 @@ fn stroke_path( let n = end.normal; let back_extrude = -n.rot90() * feathering; out.colored_vertex(p + n * outer_rad + back_extrude, color_outer); - out.colored_vertex(p + n * inner_rad, color_inner); - out.colored_vertex(p - n * inner_rad, color_inner); + out.colored_vertex( + p + n * inner_rad, + get_color(color_inner, p + n * inner_rad), + ); + out.colored_vertex( + p - n * inner_rad, + get_color(color_inner, p - n * inner_rad), + ); out.colored_vertex(p - n * outer_rad + back_extrude, color_outer); out.add_triangle(idx + 4 * i0 + 0, idx + 4 * i0 + 1, idx + 4 * i1 + 0); @@ -1067,19 +1114,39 @@ fn stroke_path( if thin_line { // Fade out thin lines rather than making them thinner let radius = feathering / 2.0; - let color = mul_color(stroke.color, stroke.width / feathering); - if color == Color32::TRANSPARENT { - return; + if let ColorMode::Solid(color) = stroke.color { + let color = mul_color(color, stroke.width / feathering); + if color == Color32::TRANSPARENT { + return; + } } for p in path { - out.colored_vertex(p.pos + radius * p.normal, color); - out.colored_vertex(p.pos - radius * p.normal, color); + out.colored_vertex( + p.pos + radius * p.normal, + mul_color( + get_color(&stroke.color, p.pos + radius * p.normal), + stroke.width / feathering, + ), + ); + out.colored_vertex( + p.pos - radius * p.normal, + mul_color( + get_color(&stroke.color, p.pos - radius * p.normal), + stroke.width / feathering, + ), + ); } } else { let radius = stroke.width / 2.0; for p in path { - out.colored_vertex(p.pos + radius * p.normal, stroke.color); - out.colored_vertex(p.pos - radius * p.normal, stroke.color); + out.colored_vertex( + p.pos + radius * p.normal, + get_color(&stroke.color, p.pos + radius * p.normal), + ); + out.colored_vertex( + p.pos - radius * p.normal, + get_color(&stroke.color, p.pos - radius * p.normal), + ); } } } @@ -1275,9 +1342,9 @@ impl Tessellator { self.tessellate_text(&text_shape, out); } Shape::QuadraticBezier(quadratic_shape) => { - self.tessellate_quadratic_bezier(quadratic_shape, out); + self.tessellate_quadratic_bezier(&quadratic_shape, out); } - Shape::CubicBezier(cubic_shape) => self.tessellate_cubic_bezier(cubic_shape, out), + Shape::CubicBezier(cubic_shape) => self.tessellate_cubic_bezier(&cubic_shape, out), Shape::Callback(_) => { panic!("Shape::Callback passed to Tessellator"); } @@ -1337,7 +1404,7 @@ impl Tessellator { self.scratchpad_path.add_circle(center, radius); self.scratchpad_path.fill(self.feathering, fill, out); self.scratchpad_path - .stroke_closed(self.feathering, stroke, out); + .stroke_closed(self.feathering, &stroke.into(), out); } /// Tessellate a single [`EllipseShape`] into a [`Mesh`]. @@ -1404,7 +1471,7 @@ impl Tessellator { self.scratchpad_path.add_line_loop(&points); self.scratchpad_path.fill(self.feathering, fill, out); self.scratchpad_path - .stroke_closed(self.feathering, stroke, out); + .stroke_closed(self.feathering, &stroke.into(), out); } /// Tessellate a single [`Mesh`] into a [`Mesh`]. @@ -1430,7 +1497,13 @@ impl Tessellator { /// /// * `shape`: the mesh to tessellate. /// * `out`: triangles are appended to this. - pub fn tessellate_line(&mut self, points: [Pos2; 2], stroke: Stroke, out: &mut Mesh) { + pub fn tessellate_line( + &mut self, + points: [Pos2; 2], + stroke: impl Into, + out: &mut Mesh, + ) { + let stroke = stroke.into(); if stroke.is_empty() { return; } @@ -1446,7 +1519,7 @@ impl Tessellator { self.scratchpad_path.clear(); self.scratchpad_path.add_line_segment(points); self.scratchpad_path - .stroke_open(self.feathering, stroke, out); + .stroke_open(self.feathering, &stroke, out); } /// Tessellate a single [`PathShape`] into a [`Mesh`]. @@ -1493,7 +1566,7 @@ impl Tessellator { PathType::Open }; self.scratchpad_path - .stroke(self.feathering, typ, *stroke, out); + .stroke(self.feathering, typ, stroke, out); } /// Tessellate a single [`Rect`] into a [`Mesh`]. @@ -1588,7 +1661,7 @@ impl Tessellator { path.fill(self.feathering, fill, out); } - path.stroke_closed(self.feathering, stroke, out); + path.stroke_closed(self.feathering, &stroke.into(), out); } self.feathering = old_feathering; // restore @@ -1707,8 +1780,11 @@ impl Tessellator { self.scratchpad_path.clear(); self.scratchpad_path .add_line_segment([row_rect.left_bottom(), row_rect.right_bottom()]); - self.scratchpad_path - .stroke_open(self.feathering, *underline, out); + self.scratchpad_path.stroke_open( + self.feathering, + &PathStroke::from(*underline), + out, + ); } } } @@ -1719,7 +1795,7 @@ impl Tessellator { /// * `out`: triangles are appended to this. pub fn tessellate_quadratic_bezier( &mut self, - quadratic_shape: QuadraticBezierShape, + quadratic_shape: &QuadraticBezierShape, out: &mut Mesh, ) { let options = &self.options; @@ -1737,7 +1813,7 @@ impl Tessellator { &points, quadratic_shape.fill, quadratic_shape.closed, - quadratic_shape.stroke, + &quadratic_shape.stroke, out, ); } @@ -1746,7 +1822,7 @@ impl Tessellator { /// /// * `cubic_shape`: the shape to tessellate. /// * `out`: triangles are appended to this. - pub fn tessellate_cubic_bezier(&mut self, cubic_shape: CubicBezierShape, out: &mut Mesh) { + pub fn tessellate_cubic_bezier(&mut self, cubic_shape: &CubicBezierShape, out: &mut Mesh) { let options = &self.options; let clip_rect = self.clip_rect; if options.coarse_tessellation_culling @@ -1763,7 +1839,7 @@ impl Tessellator { &points, cubic_shape.fill, cubic_shape.closed, - cubic_shape.stroke, + &cubic_shape.stroke, out, ); } @@ -1774,7 +1850,7 @@ impl Tessellator { points: &[Pos2], fill: Color32, closed: bool, - stroke: Stroke, + stroke: &PathStroke, out: &mut Mesh, ) { if points.len() < 2 { @@ -1985,3 +2061,48 @@ fn test_tessellator() { assert_eq!(primitives.len(), 2); } + +#[test] +fn path_bounding_box() { + use crate::*; + + for i in 1..=100 { + let width = i as f32; + + let rect = Rect::from_min_max(pos2(0.0, 0.0), pos2(10.0, 10.0)); + let expected_rect = rect.expand((width / 2.0) + 1.5); + + let mut mesh = Mesh::default(); + + let mut path = Path::default(); + path.add_open_points(&[ + pos2(0.0, 0.0), + pos2(2.0, 0.0), + pos2(5.0, 5.0), + pos2(0.0, 5.0), + pos2(0.0, 7.0), + pos2(10.0, 10.0), + ]); + + path.stroke( + 1.5, + PathType::Closed, + &PathStroke::new_uv(width, move |r, p| { + assert_eq!(r, expected_rect); + // see https://github.com/emilk/egui/pull/4353#discussion_r1573879940 for why .contains() isn't used here. + // TL;DR rounding errors. + assert!( + r.distance_to_pos(p) <= 0.55, + "passed rect {r:?} didn't contain point {p:?} (distance: {})", + r.distance_to_pos(p) + ); + assert!( + expected_rect.distance_to_pos(p) <= 0.55, + "expected rect {expected_rect:?} didn't contain point {p:?}" + ); + Color32::WHITE + }), + &mut mesh, + ); + } +} diff --git a/crates/epaint/src/text/text_layout.rs b/crates/epaint/src/text/text_layout.rs index c9956aac197..276f3f9e2a3 100644 --- a/crates/epaint/src/text/text_layout.rs +++ b/crates/epaint/src/text/text_layout.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use emath::*; -use crate::{text::font::Font, Color32, Mesh, Stroke, Vertex}; +use crate::{stroke::PathStroke, text::font::Font, Color32, Mesh, Stroke, Vertex}; use super::{FontsImpl, Galley, Glyph, LayoutJob, LayoutSection, Row, RowVisuals}; @@ -853,7 +853,7 @@ fn add_hline(point_scale: PointScale, [start, stop]: [Pos2; 2], stroke: Stroke, let mut path = crate::tessellator::Path::default(); // TODO(emilk): reuse this to avoid re-allocations. path.add_line_segment([start, stop]); let feathering = 1.0 / point_scale.pixels_per_point(); - path.stroke_open(feathering, stroke, mesh); + path.stroke_open(feathering, &PathStroke::from(stroke), mesh); } else { // Thin lines often lost, so this is a bad idea From 2c590636b5422c010cbb3f909c8eb4777a6e2f4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=96R=C3=96K=20Attila?= Date: Tue, 23 Apr 2024 08:03:36 +0200 Subject: [PATCH 109/134] `egui-winit`: Update `webbrowser` to `v1.0.0` (#4394) No significant changes, mostly marks API stability: https://github.com/amodm/webbrowser-rs/releases/tag/v1.0.0 --- Cargo.lock | 4 ++-- crates/egui-winit/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d64d54397f1..a9214151c0a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4205,9 +4205,9 @@ dependencies = [ [[package]] name = "webbrowser" -version = "0.8.11" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2c79b77f525a2d670cb40619d7d9c673d09e0666f72c591ebd7861f84a87e57" +checksum = "60b6f804e41d0852e16d2eaee61c7e4f7d3e8ffdb7b8ed85886aeb0791fe9fcd" dependencies = [ "core-foundation", "home", diff --git a/crates/egui-winit/Cargo.toml b/crates/egui-winit/Cargo.toml index fdff0fcdcbf..a09829699fc 100644 --- a/crates/egui-winit/Cargo.toml +++ b/crates/egui-winit/Cargo.toml @@ -71,7 +71,7 @@ document-features = { workspace = true, optional = true } puffin = { workspace = true, optional = true } serde = { version = "1.0", optional = true, features = ["derive"] } -webbrowser = { version = "0.8.3", optional = true } +webbrowser = { version = "1.0.0", optional = true } [target.'cfg(any(target_os="linux", target_os="dragonfly", target_os="freebsd", target_os="netbsd", target_os="openbsd"))'.dependencies] smithay-clipboard = { version = "0.7.0", optional = true } From 14194f5d3a5d3c10a09822d3234d2e03e737a0db Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 23 Apr 2024 17:35:12 +0200 Subject: [PATCH 110/134] eframe: Use `objc2` and its framework crates (#4395) These are a replacement to the `objc` and `cocoa` crates. This PR prevents: - An extra copy when creating `NSData` - A memory leak when creating `NSImage` - A memory leak when creating `NSString` And is generally a readability improvement. Note that we define `NSApp` manually for now, the implementation in `objc2-app-kit` is currently suboptimal and wouldn't allow you to check whether the NSApplication has been created or not. Related: https://github.com/emilk/egui/issues/4219, this should nicely coincide with the Winit `0.30` release. --------- Co-authored-by: Emil Ernerfeldt --- Cargo.lock | 101 +++++++++++++++++---------- crates/eframe/Cargo.toml | 15 +++- crates/eframe/src/native/app_icon.rs | 43 +++++------- deny.toml | 3 +- 4 files changed, 99 insertions(+), 63 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a9214151c0a..1afbd85fbd0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -548,7 +548,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dd7cf50912cddc06dc5ea7c08c5e81c1b2c842a70d19def1848d54c586fed92" dependencies = [ - "objc-sys 0.3.1", + "objc-sys 0.3.3", ] [[package]] @@ -571,6 +571,15 @@ dependencies = [ "objc2 0.4.1", ] +[[package]] +name = "block2" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43ff7d91d3c1d568065b06c899777d1e48dcf76103a672a0adbc238a7f247f1e" +dependencies = [ + "objc2 0.5.1", +] + [[package]] name = "blocking" version = "1.4.0" @@ -795,36 +804,6 @@ dependencies = [ "error-code", ] -[[package]] -name = "cocoa" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" -dependencies = [ - "bitflags 1.3.2", - "block", - "cocoa-foundation", - "core-foundation", - "core-graphics", - "foreign-types", - "libc", - "objc", -] - -[[package]] -name = "cocoa-foundation" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" -dependencies = [ - "bitflags 1.3.2", - "block", - "core-foundation", - "core-graphics-types", - "libc", - "objc", -] - [[package]] name = "codespan-reporting" version = "0.11.1" @@ -1201,7 +1180,6 @@ name = "eframe" version = "0.27.2" dependencies = [ "bytemuck", - "cocoa", "directories-next", "document-features", "egui", @@ -1214,7 +1192,9 @@ dependencies = [ "image", "js-sys", "log", - "objc", + "objc2 0.5.1", + "objc2-app-kit", + "objc2-foundation", "parking_lot", "percent-encoding", "pollster", @@ -2599,9 +2579,9 @@ checksum = "df3b9834c1e95694a05a828b59f55fa2afec6288359cda67146126b3f90a55d7" [[package]] name = "objc-sys" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99e1d07c6eab1ce8b6382b8e3c7246fe117ff3f8b34be065f5ebace6749fe845" +checksum = "da284c198fb9b7b0603f8635185e85fbd5b64ee154b1ed406d489077de2d6d60" [[package]] name = "objc2" @@ -2620,10 +2600,43 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "559c5a40fdd30eb5e344fbceacf7595a81e242529fb4e21cf5f43fb4f11ff98d" dependencies = [ - "objc-sys 0.3.1", + "objc-sys 0.3.3", "objc2-encode 3.0.0", ] +[[package]] +name = "objc2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4b25e1034d0e636cd84707ccdaa9f81243d399196b8a773946dcffec0401659" +dependencies = [ + "objc-sys 0.3.3", + "objc2-encode 4.0.1", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb79768a710a9a1798848179edb186d1af7e8a8679f369e4b8d201dd2a034047" +dependencies = [ + "block2 0.5.0", + "objc2 0.5.1", + "objc2-core-data", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e092bc42eaf30a08844e6a076938c60751225ec81431ab89f5d1ccd9f958d6c" +dependencies = [ + "block2 0.5.0", + "objc2 0.5.1", + "objc2-foundation", +] + [[package]] name = "objc2-encode" version = "2.0.0-pre.2" @@ -2639,6 +2652,22 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d079845b37af429bfe5dfa76e6d087d788031045b25cfc6fd898486fd9847666" +[[package]] +name = "objc2-encode" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88658da63e4cc2c8adb1262902cd6af51094df0488b760d6fd27194269c0950a" + +[[package]] +name = "objc2-foundation" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfaefe14254871ea16c7d88968c0ff14ba554712a20d76421eec52f0a7fb8904" +dependencies = [ + "block2 0.5.0", + "objc2 0.5.1", +] + [[package]] name = "objc_exception" version = "0.1.2" diff --git a/crates/eframe/Cargo.toml b/crates/eframe/Cargo.toml index da1aea6badf..5b2651c9ad4 100644 --- a/crates/eframe/Cargo.toml +++ b/crates/eframe/Cargo.toml @@ -178,8 +178,19 @@ wgpu = { workspace = true, optional = true, features = [ # mac: [target.'cfg(any(target_os = "macos"))'.dependencies] -cocoa = "0.25.0" -objc = "0.2.7" +objc2 = "0.5.1" +objc2-foundation = { version = "0.2.0", features = [ + "block2", + "NSData", + "NSString", +] } +objc2-app-kit = { version = "0.2.0", features = [ + "NSApplication", + "NSImage", + "NSMenu", + "NSMenuItem", + "NSResponder", +] } # windows: [target.'cfg(any(target_os = "windows"))'.dependencies] diff --git a/crates/eframe/src/native/app_icon.rs b/crates/eframe/src/native/app_icon.rs index f169420863c..4986549cc1b 100644 --- a/crates/eframe/src/native/app_icon.rs +++ b/crates/eframe/src/native/app_icon.rs @@ -203,12 +203,9 @@ fn set_title_and_icon_mac(title: &str, icon_data: Option<&IconData>) -> AppIconS use crate::icon_data::IconDataExt as _; crate::profile_function!(); - use cocoa::{ - appkit::{NSApp, NSApplication, NSImage, NSMenu, NSWindow}, - base::{id, nil}, - foundation::{NSData, NSString}, - }; - use objc::{msg_send, sel, sel_impl}; + use objc2::ClassType; + use objc2_app_kit::{NSApplication, NSImage}; + use objc2_foundation::{NSData, NSString}; let png_bytes = if let Some(icon_data) = icon_data { match icon_data.to_png_bytes() { @@ -222,38 +219,36 @@ fn set_title_and_icon_mac(title: &str, icon_data: Option<&IconData>) -> AppIconS None }; - // SAFETY: Accessing raw data from icon in a read-only manner. Icon data is static! + // TODO(madsmtm): Move this into `objc2-app-kit` + extern "C" { + static NSApp: Option<&'static NSApplication>; + } + unsafe { - let app = NSApp(); - if app.is_null() { + let app = if let Some(app) = NSApp { + app + } else { log::debug!("NSApp is null"); return AppIconStatus::NotSetIgnored; - } + }; if let Some(png_bytes) = png_bytes { - let data = NSData::dataWithBytes_length_( - nil, - png_bytes.as_ptr().cast::(), - png_bytes.len() as u64, - ); + let data = NSData::from_vec(png_bytes); log::trace!("NSImage::initWithData…"); - let app_icon = NSImage::initWithData_(NSImage::alloc(nil), data); + let app_icon = NSImage::initWithData(NSImage::alloc(), &data); crate::profile_scope!("setApplicationIconImage_"); log::trace!("setApplicationIconImage…"); - app.setApplicationIconImage_(app_icon); + app.setApplicationIconImage(app_icon.as_deref()); } // Change the title in the top bar - for python processes this would be again "python" otherwise. - let main_menu = app.mainMenu(); - if !main_menu.is_null() { - let item = main_menu.itemAtIndex_(0); - if !item.is_null() { - let app_menu: id = msg_send![item, submenu]; - if !app_menu.is_null() { + if let Some(main_menu) = app.mainMenu() { + if let Some(item) = main_menu.itemAtIndex(0) { + if let Some(app_menu) = item.submenu() { crate::profile_scope!("setTitle_"); - app_menu.setTitle_(NSString::alloc(nil).init_str(title)); + app_menu.setTitle(&NSString::from_str(title)); } } } diff --git a/deny.toml b/deny.toml index ebe372744dd..02a9cdb04a3 100644 --- a/deny.toml +++ b/deny.toml @@ -47,6 +47,7 @@ deny = [ skip = [ { name = "bitflags" }, # old 1.0 version via glutin, png, spirv, … + { name = "block2" }, # old version via glutin->icrate { name = "event-listener" }, # TODO(emilk): rustls pulls in two versions of this 😭 { name = "libloading" }, # wgpu-hal itself depends on 0.8 while some of its dependencies, like ash and d3d12, depend on 0.7 { name = "memoffset" }, # tiny dependency @@ -61,7 +62,7 @@ skip = [ skip-tree = [ { name = "criterion" }, # dev-dependency { name = "fastrand" }, # old version via accesskit_unix - { name = "foreign-types" }, # small crate. Old version via cocoa and core-graphics (winit). + { name = "foreign-types" }, # small crate. Old version via core-graphics (winit). { name = "objc2" }, # old version via accesskit_macos { name = "polling" }, # old version via accesskit_unix { name = "rfd" }, # example dependency From bfe1858e0bdd3ee50a9d594fe9351476ede44241 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dennis=20Sch=C3=B6n?= Date: Thu, 25 Apr 2024 15:43:24 +0200 Subject: [PATCH 111/134] eframe: update ViewportBuilder.with_icon() documentation (#4408) `None` is no longer correct. To disable the egui icon one has to pass `IconData::default()` now. --- crates/egui/src/viewport.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/egui/src/viewport.rs b/crates/egui/src/viewport.rs index 090e93b3fe5..e1e3ebf0e82 100644 --- a/crates/egui/src/viewport.rs +++ b/crates/egui/src/viewport.rs @@ -389,7 +389,7 @@ impl ViewportBuilder { /// The application icon, e.g. in the Windows task bar or the alt-tab menu. /// /// The default icon is a white `e` on a black background (for "egui" or "eframe"). - /// If you prefer the OS default, set this to `None`. + /// If you prefer the OS default, set this to `IconData::default()`. #[inline] pub fn with_icon(mut self, icon: impl Into>) -> Self { self.icon = Some(icon.into()); From cee790681d10971afdbb94aa734db71fd9ce28e6 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 25 Apr 2024 15:51:01 +0200 Subject: [PATCH 112/134] Update to Rust 1.76 (#4411) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Motivation: I want to replace `cargo-cranky` with workspace lints, first available in Rust 1.74. However, `cargo doc` would hange on `wgpu` and `wgpu-core` on 1.74 and 1.75… so now we're on 1.76. I think this is fine - when 1.78 is released next week we're still two versions behind the bleeding edge. …and the branch name is just wrong 🤦 --- .github/workflows/deploy_web_demo.yml | 2 +- .github/workflows/rust.yml | 10 +++++----- Cargo.toml | 2 +- clippy.toml | 2 +- crates/eframe/src/native/app_icon.rs | 4 +--- crates/eframe/src/native/run.rs | 4 ++++ crates/eframe/src/web/storage.rs | 2 +- crates/egui/src/containers/collapsing_header.rs | 2 +- crates/egui/src/lib.rs | 4 ++-- crates/egui/src/memory.rs | 2 +- crates/egui/src/style.rs | 2 +- crates/egui_extras/src/table.rs | 2 +- crates/epaint/src/shape.rs | 7 +------ examples/confirm_exit/Cargo.toml | 2 +- examples/custom_3d_glow/Cargo.toml | 2 +- examples/custom_font/Cargo.toml | 2 +- examples/custom_font_style/Cargo.toml | 2 +- examples/custom_keypad/Cargo.toml | 2 +- examples/custom_plot_manipulation/Cargo.toml | 2 +- examples/custom_window_frame/Cargo.toml | 2 +- examples/file_dialog/Cargo.toml | 2 +- examples/hello_world/Cargo.toml | 2 +- examples/hello_world_par/Cargo.toml | 2 +- examples/hello_world_simple/Cargo.toml | 2 +- examples/images/Cargo.toml | 2 +- examples/images/src/ferris.svg | 2 +- examples/keyboard_events/Cargo.toml | 2 +- examples/multiple_viewports/Cargo.toml | 2 +- examples/puffin_profiler/Cargo.toml | 2 +- examples/save_plot/Cargo.toml | 2 +- examples/screenshot/Cargo.toml | 2 +- examples/serial_windows/Cargo.toml | 2 +- examples/test_inline_glow_paint/Cargo.toml | 2 +- examples/test_viewports/Cargo.toml | 2 +- examples/user_attention/Cargo.toml | 2 +- rust-toolchain | 2 +- scripts/clippy_wasm/clippy.toml | 2 +- 37 files changed, 45 insertions(+), 48 deletions(-) diff --git a/.github/workflows/deploy_web_demo.yml b/.github/workflows/deploy_web_demo.yml index ea2956db313..41203da390b 100644 --- a/.github/workflows/deploy_web_demo.yml +++ b/.github/workflows/deploy_web_demo.yml @@ -43,7 +43,7 @@ jobs: with: profile: minimal target: wasm32-unknown-unknown - toolchain: 1.72.0 + toolchain: 1.76.0 override: true - uses: Swatinem/rust-cache@v2 diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index d641b17b284..89983a2c48b 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -19,7 +19,7 @@ jobs: - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.72.0 + toolchain: 1.76.0 - name: Install packages (Linux) if: runner.os == 'Linux' @@ -93,7 +93,7 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.72.0 + toolchain: 1.76.0 targets: wasm32-unknown-unknown - run: sudo apt-get update && sudo apt-get install libgtk-3-dev libatk1.0-dev @@ -151,7 +151,7 @@ jobs: - uses: actions/checkout@v4 - uses: EmbarkStudios/cargo-deny-action@v1 with: - rust-version: "1.72.0" + rust-version: "1.76.0" log-level: error command: check arguments: --target ${{ matrix.target }} @@ -166,7 +166,7 @@ jobs: - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.72.0 + toolchain: 1.76.0 targets: aarch64-linux-android - name: Set up cargo cache @@ -184,7 +184,7 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.72.0 + toolchain: 1.76.0 - name: Set up cargo cache uses: Swatinem/rust-cache@v2 diff --git a/Cargo.toml b/Cargo.toml index 425eb8de208..47ef422cb5a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ members = [ [workspace.package] edition = "2021" license = "MIT OR Apache-2.0" -rust-version = "1.72" +rust-version = "1.76" version = "0.27.2" diff --git a/clippy.toml b/clippy.toml index 93d7874068e..d91f9666c47 100644 --- a/clippy.toml +++ b/clippy.toml @@ -3,7 +3,7 @@ # ----------------------------------------------------------------------------- # Section identical to scripts/clippy_wasm/clippy.toml: -msrv = "1.72" +msrv = "1.76" allow-unwrap-in-tests = true diff --git a/crates/eframe/src/native/app_icon.rs b/crates/eframe/src/native/app_icon.rs index 4986549cc1b..39c32dfdb3e 100644 --- a/crates/eframe/src/native/app_icon.rs +++ b/crates/eframe/src/native/app_icon.rs @@ -225,9 +225,7 @@ fn set_title_and_icon_mac(title: &str, icon_data: Option<&IconData>) -> AppIconS } unsafe { - let app = if let Some(app) = NSApp { - app - } else { + let Some(app) = NSApp else { log::debug!("NSApp is null"); return AppIconStatus::NotSetIgnored; }; diff --git a/crates/eframe/src/native/run.rs b/crates/eframe/src/native/run.rs index 896d640de36..3ee249edf76 100644 --- a/crates/eframe/src/native/run.rs +++ b/crates/eframe/src/native/run.rs @@ -391,6 +391,8 @@ pub fn run_glow( mut native_options: epi::NativeOptions, app_creator: epi::AppCreator, ) -> Result<()> { + #![allow(clippy::needless_return_with_question_mark)] // False positive + use super::glow_integration::GlowWinitApp; #[cfg(not(target_os = "ios"))] @@ -414,6 +416,8 @@ pub fn run_wgpu( mut native_options: epi::NativeOptions, app_creator: epi::AppCreator, ) -> Result<()> { + #![allow(clippy::needless_return_with_question_mark)] // False positive + use super::wgpu_integration::WgpuWinitApp; #[cfg(not(target_os = "ios"))] diff --git a/crates/eframe/src/web/storage.rs b/crates/eframe/src/web/storage.rs index 4a2a53326a1..170798dc662 100644 --- a/crates/eframe/src/web/storage.rs +++ b/crates/eframe/src/web/storage.rs @@ -31,7 +31,7 @@ pub(crate) fn load_memory(_: &egui::Context) {} #[cfg(feature = "persistence")] pub(crate) fn save_memory(ctx: &egui::Context) { - match ctx.memory(|mem| ron::to_string(mem)) { + match ctx.memory(ron::to_string) { Ok(ron) => { local_storage_set("egui_memory_ron", &ron); } diff --git a/crates/egui/src/containers/collapsing_header.rs b/crates/egui/src/containers/collapsing_header.rs index 6fb7662af22..7c541a1a2f7 100644 --- a/crates/egui/src/containers/collapsing_header.rs +++ b/crates/egui/src/containers/collapsing_header.rs @@ -230,7 +230,7 @@ impl CollapsingState { } } - /// Paint this [CollapsingState](CollapsingState)'s toggle button. Takes an [IconPainter](IconPainter) as the icon. + /// Paint this [`CollapsingState`]'s toggle button. Takes an [`IconPainter`] as the icon. /// ``` /// # egui::__run_test_ui(|ui| { /// fn circle_icon(ui: &mut egui::Ui, openness: f32, response: &egui::Response) { diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index b8f0c8fa3d2..30e3b804f11 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -517,7 +517,7 @@ macro_rules! include_image { }; } -/// Create a [`Hyperlink`](crate::Hyperlink) to the current [`file!()`] (and line) on Github +/// Create a [`Hyperlink`] to the current [`file!()`] (and line) on Github /// /// ``` /// # egui::__run_test_ui(|ui| { @@ -532,7 +532,7 @@ macro_rules! github_link_file_line { }}; } -/// Create a [`Hyperlink`](crate::Hyperlink) to the current [`file!()`] on github. +/// Create a [`Hyperlink`] to the current [`file!()`] on github. /// /// ``` /// # egui::__run_test_ui(|ui| { diff --git a/crates/egui/src/memory.rs b/crates/egui/src/memory.rs index 7d61acd965f..2ef8bbf58ba 100644 --- a/crates/egui/src/memory.rs +++ b/crates/egui/src/memory.rs @@ -868,7 +868,7 @@ impl Memory { // ---------------------------------------------------------------------------- /// Keeps track of [`Area`](crate::containers::area::Area)s, which are free-floating [`Ui`](crate::Ui)s. -/// These [`Area`](crate::containers::area::Area)s can be in any [`Order`](crate::Order). +/// These [`Area`](crate::containers::area::Area)s can be in any [`Order`]. #[derive(Clone, Debug, Default)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", serde(default))] diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index 9f9aac0534a..aad8e3eea2d 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -288,7 +288,7 @@ pub struct Spacing { /// Default rail height of a [`Slider`]. pub slider_rail_height: f32, - /// Default (minimum) width of a [`ComboBox`](crate::ComboBox). + /// Default (minimum) width of a [`ComboBox`]. pub combo_width: f32, /// Default width of a [`TextEdit`]. diff --git a/crates/egui_extras/src/table.rs b/crates/egui_extras/src/table.rs index 5e9cf80fdcd..0cbf5e13b5f 100644 --- a/crates/egui_extras/src/table.rs +++ b/crates/egui_extras/src/table.rs @@ -131,7 +131,7 @@ impl Column { self } - /// Allowed range of movement (in points), if in a resizable [`Table`](crate::table::Table). + /// Allowed range of movement (in points), if in a resizable [`Table`]. #[inline] pub fn range(mut self, range: impl Into) -> Self { self.width_range = range.into(); diff --git a/crates/epaint/src/shape.rs b/crates/epaint/src/shape.rs index 336c9087316..593cc78b0f3 100644 --- a/crates/epaint/src/shape.rs +++ b/crates/epaint/src/shape.rs @@ -1293,12 +1293,7 @@ impl std::fmt::Debug for PaintCallback { impl std::cmp::PartialEq for PaintCallback { fn eq(&self, other: &Self) -> bool { - // As I understand it, the problem this clippy is trying to protect against - // can only happen if we do dynamic casts back and forth on the pointers, and we don't do that. - #[allow(clippy::vtable_address_comparisons)] - { - self.rect.eq(&other.rect) && Arc::ptr_eq(&self.callback, &other.callback) - } + self.rect.eq(&other.rect) && Arc::ptr_eq(&self.callback, &other.callback) } } diff --git a/examples/confirm_exit/Cargo.toml b/examples/confirm_exit/Cargo.toml index 09ad2c4f606..8e9d3af2872 100644 --- a/examples/confirm_exit/Cargo.toml +++ b/examples/confirm_exit/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.72" +rust-version = "1.76" publish = false diff --git a/examples/custom_3d_glow/Cargo.toml b/examples/custom_3d_glow/Cargo.toml index d7e70401a47..ef3d1cbc828 100644 --- a/examples/custom_3d_glow/Cargo.toml +++ b/examples/custom_3d_glow/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.72" +rust-version = "1.76" publish = false diff --git a/examples/custom_font/Cargo.toml b/examples/custom_font/Cargo.toml index db633431531..89840c4526d 100644 --- a/examples/custom_font/Cargo.toml +++ b/examples/custom_font/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.72" +rust-version = "1.76" publish = false diff --git a/examples/custom_font_style/Cargo.toml b/examples/custom_font_style/Cargo.toml index 51990ee919d..60e3e37b9c2 100644 --- a/examples/custom_font_style/Cargo.toml +++ b/examples/custom_font_style/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["tami5 "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.72" +rust-version = "1.76" publish = false diff --git a/examples/custom_keypad/Cargo.toml b/examples/custom_keypad/Cargo.toml index 1557b35c86c..f008884c5c7 100644 --- a/examples/custom_keypad/Cargo.toml +++ b/examples/custom_keypad/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Varphone Wong "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.72" +rust-version = "1.76" publish = false diff --git a/examples/custom_plot_manipulation/Cargo.toml b/examples/custom_plot_manipulation/Cargo.toml index 3df5de96f3d..3db38a45b92 100644 --- a/examples/custom_plot_manipulation/Cargo.toml +++ b/examples/custom_plot_manipulation/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Ygor Souza "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.72" +rust-version = "1.76" publish = false diff --git a/examples/custom_window_frame/Cargo.toml b/examples/custom_window_frame/Cargo.toml index e163bda492a..c334c6a662b 100644 --- a/examples/custom_window_frame/Cargo.toml +++ b/examples/custom_window_frame/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.72" +rust-version = "1.76" publish = false diff --git a/examples/file_dialog/Cargo.toml b/examples/file_dialog/Cargo.toml index ceafd1be28a..ef30dfce8e1 100644 --- a/examples/file_dialog/Cargo.toml +++ b/examples/file_dialog/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.72" +rust-version = "1.76" publish = false diff --git a/examples/hello_world/Cargo.toml b/examples/hello_world/Cargo.toml index e9e113c6722..acf7e2b8ab8 100644 --- a/examples/hello_world/Cargo.toml +++ b/examples/hello_world/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.72" +rust-version = "1.76" publish = false diff --git a/examples/hello_world_par/Cargo.toml b/examples/hello_world_par/Cargo.toml index 442f671e330..3d0e92eaec4 100644 --- a/examples/hello_world_par/Cargo.toml +++ b/examples/hello_world_par/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Maxim Osipenko "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.72" +rust-version = "1.76" publish = false diff --git a/examples/hello_world_simple/Cargo.toml b/examples/hello_world_simple/Cargo.toml index 5b93eb686c7..bec4670b790 100644 --- a/examples/hello_world_simple/Cargo.toml +++ b/examples/hello_world_simple/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.72" +rust-version = "1.76" publish = false diff --git a/examples/images/Cargo.toml b/examples/images/Cargo.toml index b6cb142f723..cd9157f5715 100644 --- a/examples/images/Cargo.toml +++ b/examples/images/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Jan Procházka "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.72" +rust-version = "1.76" publish = false diff --git a/examples/images/src/ferris.svg b/examples/images/src/ferris.svg index c7f240dd97a..fe1589d912c 100644 --- a/examples/images/src/ferris.svg +++ b/examples/images/src/ferris.svg @@ -3,7 +3,7 @@ - + diff --git a/examples/keyboard_events/Cargo.toml b/examples/keyboard_events/Cargo.toml index 5428bc6a3ae..08f7ba3a81e 100644 --- a/examples/keyboard_events/Cargo.toml +++ b/examples/keyboard_events/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Jose Palazon "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.72" +rust-version = "1.76" publish = false diff --git a/examples/multiple_viewports/Cargo.toml b/examples/multiple_viewports/Cargo.toml index 37b27cb5f96..b5bd8e80411 100644 --- a/examples/multiple_viewports/Cargo.toml +++ b/examples/multiple_viewports/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.72" +rust-version = "1.76" publish = false [features] diff --git a/examples/puffin_profiler/Cargo.toml b/examples/puffin_profiler/Cargo.toml index 021c05651a6..6b6acad726d 100644 --- a/examples/puffin_profiler/Cargo.toml +++ b/examples/puffin_profiler/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.72" +rust-version = "1.76" publish = false diff --git a/examples/save_plot/Cargo.toml b/examples/save_plot/Cargo.toml index cb31594343c..7c9eb245285 100644 --- a/examples/save_plot/Cargo.toml +++ b/examples/save_plot/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["hacknus "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.72" +rust-version = "1.76" publish = false [dependencies] diff --git a/examples/screenshot/Cargo.toml b/examples/screenshot/Cargo.toml index 5c7bbbeec42..0fb5da7fd2a 100644 --- a/examples/screenshot/Cargo.toml +++ b/examples/screenshot/Cargo.toml @@ -7,7 +7,7 @@ authors = [ ] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.72" +rust-version = "1.76" publish = false diff --git a/examples/serial_windows/Cargo.toml b/examples/serial_windows/Cargo.toml index ee0f7b78d25..58d5cdecced 100644 --- a/examples/serial_windows/Cargo.toml +++ b/examples/serial_windows/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.72" +rust-version = "1.76" publish = false diff --git a/examples/test_inline_glow_paint/Cargo.toml b/examples/test_inline_glow_paint/Cargo.toml index f1ff645ac27..893b0d07de1 100644 --- a/examples/test_inline_glow_paint/Cargo.toml +++ b/examples/test_inline_glow_paint/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.72" +rust-version = "1.76" publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/examples/test_viewports/Cargo.toml b/examples/test_viewports/Cargo.toml index cd5b2be0e18..4e106e37ec8 100644 --- a/examples/test_viewports/Cargo.toml +++ b/examples/test_viewports/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["konkitoman"] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.72" +rust-version = "1.76" publish = false [features] diff --git a/examples/user_attention/Cargo.toml b/examples/user_attention/Cargo.toml index 02a776477dd..10f0ce711c0 100644 --- a/examples/user_attention/Cargo.toml +++ b/examples/user_attention/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["TicClick "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.72" +rust-version = "1.76" publish = false [dependencies] diff --git a/rust-toolchain b/rust-toolchain index 694e5af99e5..871f562485d 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -5,6 +5,6 @@ # to the user in the error, instead of "error: invalid channel name '[toolchain]'". [toolchain] -channel = "1.72.0" +channel = "1.76.0" components = ["rustfmt", "clippy"] targets = ["wasm32-unknown-unknown"] diff --git a/scripts/clippy_wasm/clippy.toml b/scripts/clippy_wasm/clippy.toml index 0e8b9474ea7..943444cfc50 100644 --- a/scripts/clippy_wasm/clippy.toml +++ b/scripts/clippy_wasm/clippy.toml @@ -6,7 +6,7 @@ # ----------------------------------------------------------------------------- # Section identical to the root clippy.toml: -msrv = "1.72" +msrv = "1.76" allow-unwrap-in-tests = true From 2f508d6a6163c8b40ddded83eb2a7390d1e4c1e7 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 25 Apr 2024 17:24:50 +0200 Subject: [PATCH 113/134] Replace cargo-cranky with workspace lints (#4413) Replace `cargo-cranky` (which has served us well) with workspace lints --- .github/pull_request_template.md | 2 +- .github/workflows/rust.yml | 20 +- Cargo.toml | 166 +++++++++++++++++ Cranky.toml | 176 ------------------ bacon.toml | 74 -------- crates/ecolor/Cargo.toml | 3 + crates/eframe/Cargo.toml | 3 + crates/eframe/src/native/app_icon.rs | 1 + crates/eframe/src/native/glow_integration.rs | 6 +- crates/eframe/src/web/text_agent.rs | 2 +- crates/egui-wgpu/Cargo.toml | 3 + crates/egui-wgpu/src/winit.rs | 3 + crates/egui-winit/Cargo.toml | 3 + crates/egui-winit/src/clipboard.rs | 2 + crates/egui-winit/src/lib.rs | 5 +- crates/egui/Cargo.toml | 3 + crates/egui/src/context.rs | 4 +- crates/egui/src/data/key.rs | 12 +- crates/egui/src/load/bytes_loader.rs | 2 +- crates/egui/src/response.rs | 2 +- crates/egui/src/text_selection/visuals.rs | 2 +- crates/egui/src/widgets/text_edit/builder.rs | 6 +- crates/egui_demo_app/Cargo.toml | 3 + .../egui_demo_app/src/apps/custom3d_glow.rs | 2 + crates/egui_demo_app/src/apps/http_app.rs | 2 +- crates/egui_demo_app/src/main.rs | 1 + crates/egui_demo_lib/Cargo.toml | 3 + .../src/easy_mark/easy_mark_editor.rs | 2 +- crates/egui_extras/Cargo.toml | 3 + crates/egui_glow/Cargo.toml | 3 + crates/egui_glow/examples/pure_glow.rs | 5 +- crates/egui_glow/src/lib.rs | 1 + crates/egui_glow/src/shader_version.rs | 1 + crates/egui_plot/Cargo.toml | 3 + crates/emath/Cargo.toml | 3 + crates/epaint/Cargo.toml | 3 + examples/confirm_exit/Cargo.toml | 3 + examples/confirm_exit/src/main.rs | 1 + examples/custom_3d_glow/Cargo.toml | 3 + examples/custom_3d_glow/src/main.rs | 2 + examples/custom_font/Cargo.toml | 3 + examples/custom_font/src/main.rs | 1 + examples/custom_font_style/Cargo.toml | 3 + examples/custom_font_style/src/main.rs | 1 + examples/custom_keypad/Cargo.toml | 3 + examples/custom_keypad/src/main.rs | 1 + examples/custom_plot_manipulation/Cargo.toml | 3 + examples/custom_plot_manipulation/src/main.rs | 1 + examples/custom_window_frame/Cargo.toml | 3 + examples/custom_window_frame/src/main.rs | 1 + examples/file_dialog/Cargo.toml | 3 + examples/file_dialog/src/main.rs | 1 + examples/hello_world/Cargo.toml | 3 + examples/hello_world/src/main.rs | 1 + examples/hello_world_par/Cargo.toml | 3 + examples/hello_world_par/src/main.rs | 1 + examples/hello_world_simple/Cargo.toml | 3 + examples/hello_world_simple/src/main.rs | 1 + examples/images/Cargo.toml | 3 + examples/images/src/main.rs | 1 + examples/keyboard_events/Cargo.toml | 3 + examples/keyboard_events/src/main.rs | 1 + examples/multiple_viewports/Cargo.toml | 3 + examples/multiple_viewports/src/main.rs | 1 + examples/puffin_profiler/Cargo.toml | 3 + examples/puffin_profiler/src/main.rs | 1 + examples/save_plot/Cargo.toml | 3 + examples/save_plot/src/main.rs | 1 + examples/screenshot/Cargo.toml | 3 + examples/screenshot/src/main.rs | 1 + examples/serial_windows/Cargo.toml | 3 + examples/serial_windows/src/main.rs | 1 + examples/test_inline_glow_paint/Cargo.toml | 3 + examples/test_inline_glow_paint/src/main.rs | 4 + examples/test_viewports/Cargo.toml | 3 + examples/test_viewports/src/main.rs | 3 + examples/user_attention/Cargo.toml | 3 + examples/user_attention/src/main.rs | 3 + scripts/check.sh | 3 +- scripts/clippy_wasm.sh | 2 +- xtask/Cargo.toml | 3 + xtask/src/main.rs | 2 + 82 files changed, 348 insertions(+), 289 deletions(-) delete mode 100644 Cranky.toml delete mode 100644 bacon.toml diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index c3bc239276b..98a3b496a21 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -6,7 +6,7 @@ Please read the "Making a PR" section of [`CONTRIBUTING.md`](https://github.com/ * If applicable, add a screenshot or gif. * If it is a non-trivial addition, consider adding a demo for it to `egui_demo_lib`, or a new example. * Do NOT open PR:s from your `master` branch, as that makes it hard for maintainers to add commits to your PR. -* Remember to run `cargo fmt` and `cargo cranky`. +* Remember to run `cargo fmt` and `cargo clippy`. * Open the PR as a draft until you have self-reviewed it and run `./scripts/check.sh`. * When you have addressed a PR comment, mark it as resolved. diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 89983a2c48b..26404d294a3 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -40,11 +40,6 @@ jobs: - name: Lint vertical spacing run: ./scripts/lint.py - - name: Install cargo-cranky - uses: baptiste0928/cargo-install@v1 - with: - crate: cargo-cranky - - name: check --all-features run: cargo check --locked --all-features --all-targets @@ -78,11 +73,11 @@ jobs: - name: Test run: cargo test --all-features - - name: Cranky - run: cargo cranky --all-targets --all-features -- -D warnings + - name: clippy + run: cargo clippy --all-targets --all-features -- -D warnings - - name: Cranky release - run: cargo cranky --all-targets --all-features --release -- -D warnings + - name: clippy release + run: cargo clippy --all-targets --all-features --release -- -D warnings # --------------------------------------------------------------------------- @@ -101,11 +96,6 @@ jobs: - name: Set up cargo cache uses: Swatinem/rust-cache@v2 - - name: Install cargo-cranky - uses: baptiste0928/cargo-install@v1 - with: - crate: cargo-cranky - - name: Check wasm32 egui_demo_app run: cargo check -p egui_demo_app --lib --target wasm32-unknown-unknown @@ -122,7 +112,7 @@ jobs: - run: ./scripts/wasm_bindgen_check.sh --skip-setup - - name: Cranky wasm32 + - name: clippy wasm32 run: ./scripts/clippy_wasm.sh # --------------------------------------------------------------------------- diff --git a/Cargo.toml b/Cargo.toml index 47ef422cb5a..c94baab2843 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -84,3 +84,169 @@ wgpu = { version = "0.19.1", default-features = false, features = [ "fragile-send-sync-non-atomic-wasm", ] } winit = { version = "0.29.4", default-features = false } + + +[workspace.lints.rust] +unsafe_code = "deny" + +elided_lifetimes_in_paths = "warn" +future_incompatible = "warn" +nonstandard_style = "warn" +rust_2018_idioms = "warn" +rust_2021_prelude_collisions = "warn" +semicolon_in_expressions_from_macros = "warn" +trivial_numeric_casts = "warn" +unsafe_op_in_unsafe_fn = "warn" # `unsafe_op_in_unsafe_fn` may become the default in future Rust versions: https://github.com/rust-lang/rust/issues/71668 +unused_extern_crates = "warn" +unused_import_braces = "warn" +unused_lifetimes = "warn" + +trivial_casts = "allow" +unused_qualifications = "allow" + +[workspace.lints.rustdoc] +all = "warn" +missing_crate_level_docs = "warn" + +# See also clippy.toml +[workspace.lints.clippy] +as_ptr_cast_mut = "warn" +await_holding_lock = "warn" +bool_to_int_with_if = "warn" +char_lit_as_u8 = "warn" +checked_conversions = "warn" +clear_with_drain = "warn" +cloned_instead_of_copied = "warn" +dbg_macro = "warn" +debug_assert_with_mut_call = "warn" +derive_partial_eq_without_eq = "warn" +disallowed_macros = "warn" # See clippy.toml +disallowed_methods = "warn" # See clippy.toml +disallowed_names = "warn" # See clippy.toml +disallowed_script_idents = "warn" # See clippy.toml +disallowed_types = "warn" # See clippy.toml +doc_link_with_quotes = "warn" +doc_markdown = "warn" +empty_enum = "warn" +enum_glob_use = "warn" +equatable_if_let = "warn" +exit = "warn" +expl_impl_clone_on_copy = "warn" +explicit_deref_methods = "warn" +explicit_into_iter_loop = "warn" +explicit_iter_loop = "warn" +fallible_impl_from = "warn" +filter_map_next = "warn" +flat_map_option = "warn" +float_cmp_const = "warn" +fn_params_excessive_bools = "warn" +fn_to_numeric_cast_any = "warn" +from_iter_instead_of_collect = "warn" +get_unwrap = "warn" +if_let_mutex = "warn" +implicit_clone = "warn" +implied_bounds_in_impls = "warn" +imprecise_flops = "warn" +index_refutable_slice = "warn" +inefficient_to_string = "warn" +infinite_loop = "warn" +into_iter_without_iter = "warn" +invalid_upcast_comparisons = "warn" +iter_not_returning_iterator = "warn" +iter_on_empty_collections = "warn" +iter_on_single_items = "warn" +iter_without_into_iter = "warn" +large_digit_groups = "warn" +large_include_file = "warn" +large_stack_arrays = "warn" +large_stack_frames = "warn" +large_types_passed_by_value = "warn" +let_unit_value = "warn" +linkedlist = "warn" +lossy_float_literal = "warn" +macro_use_imports = "warn" +manual_assert = "warn" +manual_clamp = "warn" +manual_instant_elapsed = "warn" +manual_let_else = "warn" +manual_ok_or = "warn" +manual_string_new = "warn" +map_err_ignore = "warn" +map_flatten = "warn" +map_unwrap_or = "warn" +match_on_vec_items = "warn" +match_same_arms = "warn" +match_wild_err_arm = "warn" +match_wildcard_for_single_variants = "warn" +mem_forget = "warn" +mismatched_target_os = "warn" +mismatching_type_param_order = "warn" +missing_enforced_import_renames = "warn" +missing_errors_doc = "warn" +missing_safety_doc = "warn" +mut_mut = "warn" +mutex_integer = "warn" +needless_borrow = "warn" +needless_continue = "warn" +needless_for_each = "warn" +needless_pass_by_ref_mut = "warn" +needless_pass_by_value = "warn" +negative_feature_names = "warn" +nonstandard_macro_braces = "warn" +option_option = "warn" +path_buf_push_overwrite = "warn" +ptr_as_ptr = "warn" +ptr_cast_constness = "warn" +pub_without_shorthand = "warn" +rc_mutex = "warn" +readonly_write_lock = "warn" +redundant_type_annotations = "warn" +ref_option_ref = "warn" +ref_patterns = "warn" +rest_pat_in_fully_bound_structs = "warn" +same_functions_in_if_condition = "warn" +semicolon_if_nothing_returned = "warn" +single_match_else = "warn" +str_to_string = "warn" +string_add = "warn" +string_add_assign = "warn" +string_lit_as_bytes = "warn" +string_lit_chars_any = "warn" +string_to_string = "warn" +suspicious_command_arg_space = "warn" +suspicious_xor_used_as_pow = "warn" +todo = "warn" +trailing_empty_array = "warn" +trait_duplication_in_bounds = "warn" +tuple_array_conversions = "warn" +unchecked_duration_subtraction = "warn" +undocumented_unsafe_blocks = "warn" +unimplemented = "warn" +uninhabited_references = "warn" +uninlined_format_args = "warn" +unnecessary_box_returns = "warn" +unnecessary_safety_doc = "warn" +unnecessary_struct_initialization = "warn" +unnecessary_wraps = "warn" +unnested_or_patterns = "warn" +unused_peekable = "warn" +unused_rounding = "warn" +unused_self = "warn" +useless_transmute = "warn" +verbose_file_reads = "warn" +wildcard_dependencies = "warn" +zero_sized_map_values = "warn" + +# TODO(emilk): enable more of these linits: +iter_over_hash_type = "allow" +let_underscore_untyped = "allow" +missing_assert_message = "allow" +print_stderr = "allow" # TODO(emilk): use `log` crate insteaditer_over_hash_type = "allow" +should_panic_without_expect = "allow" +too_many_lines = "allow" +unwrap_used = "allow" # TODO(emilk): We really wanna warn on this one + +manual_range_contains = "allow" # this one is just worse imho +self_named_module_files = "allow" # Disabled waiting on https://github.com/rust-lang/rust-clippy/issues/9602 +significant_drop_tightening = "allow" # Too many false positives +wildcard_imports = "allow" # we do this a lot in egui diff --git a/Cranky.toml b/Cranky.toml deleted file mode 100644 index db236bff421..00000000000 --- a/Cranky.toml +++ /dev/null @@ -1,176 +0,0 @@ -# https://github.com/ericseppanen/cargo-cranky -# cargo install cargo-cranky && cargo cranky -# See also clippy.toml - -deny = ["unsafe_code"] - -warn = [ - "clippy::all", - "clippy::as_ptr_cast_mut", - "clippy::await_holding_lock", - "clippy::bool_to_int_with_if", - "clippy::branches_sharing_code", - "clippy::char_lit_as_u8", - "clippy::checked_conversions", - "clippy::clear_with_drain", - "clippy::cloned_instead_of_copied", - "clippy::dbg_macro", - "clippy::debug_assert_with_mut_call", - "clippy::default_union_representation", - "clippy::derive_partial_eq_without_eq", - "clippy::disallowed_macros", # See clippy.toml - "clippy::disallowed_methods", # See clippy.toml - "clippy::disallowed_names", # See clippy.toml - "clippy::disallowed_script_idents", # See clippy.toml - "clippy::disallowed_types", # See clippy.toml - "clippy::doc_link_with_quotes", - "clippy::doc_markdown", - "clippy::empty_enum", - "clippy::empty_line_after_outer_attr", - "clippy::enum_glob_use", - "clippy::equatable_if_let", - "clippy::exit", - "clippy::expl_impl_clone_on_copy", - "clippy::explicit_deref_methods", - "clippy::explicit_into_iter_loop", - "clippy::explicit_iter_loop", - "clippy::fallible_impl_from", - "clippy::filter_map_next", - "clippy::flat_map_option", - "clippy::float_cmp_const", - "clippy::fn_params_excessive_bools", - "clippy::fn_to_numeric_cast_any", - "clippy::from_iter_instead_of_collect", - "clippy::get_unwrap", - "clippy::if_let_mutex", - "clippy::implicit_clone", - "clippy::imprecise_flops", - "clippy::index_refutable_slice", - "clippy::inefficient_to_string", - "clippy::invalid_upcast_comparisons", - "clippy::iter_not_returning_iterator", - "clippy::iter_on_empty_collections", - "clippy::iter_on_single_items", - "clippy::large_digit_groups", - "clippy::large_include_file", - "clippy::large_stack_arrays", - "clippy::large_stack_frames", - "clippy::large_types_passed_by_value", - "clippy::let_unit_value", - "clippy::linkedlist", - "clippy::lossy_float_literal", - "clippy::macro_use_imports", - "clippy::manual_assert", - "clippy::manual_clamp", - "clippy::manual_instant_elapsed", - "clippy::manual_let_else", - "clippy::manual_ok_or", - "clippy::manual_string_new", - "clippy::map_err_ignore", - "clippy::map_flatten", - "clippy::map_unwrap_or", - "clippy::match_on_vec_items", - "clippy::match_same_arms", - "clippy::match_wild_err_arm", - "clippy::match_wildcard_for_single_variants", - "clippy::mem_forget", - "clippy::mismatched_target_os", - "clippy::mismatching_type_param_order", - "clippy::missing_enforced_import_renames", - "clippy::missing_errors_doc", - "clippy::missing_safety_doc", - "clippy::mut_mut", - "clippy::mutex_integer", - "clippy::needless_borrow", - "clippy::needless_continue", - "clippy::needless_for_each", - "clippy::needless_pass_by_value", - "clippy::negative_feature_names", - "clippy::nonstandard_macro_braces", - "clippy::option_option", - "clippy::path_buf_push_overwrite", - "clippy::print_stdout", - "clippy::ptr_as_ptr", - "clippy::ptr_cast_constness", - "clippy::pub_without_shorthand", - "clippy::rc_mutex", - "clippy::redundant_type_annotations", - "clippy::ref_option_ref", - "clippy::rest_pat_in_fully_bound_structs", - "clippy::same_functions_in_if_condition", - "clippy::semicolon_if_nothing_returned", - "clippy::significant_drop_tightening", - "clippy::single_match_else", - "clippy::str_to_string", - "clippy::string_add_assign", - "clippy::string_add", - "clippy::string_lit_as_bytes", - "clippy::string_to_string", - "clippy::suspicious_command_arg_space", - "clippy::suspicious_xor_used_as_pow", - "clippy::todo", - "clippy::trailing_empty_array", - "clippy::trait_duplication_in_bounds", - "clippy::transmute_ptr_to_ptr", - "clippy::tuple_array_conversions", - "clippy::unchecked_duration_subtraction", - "clippy::undocumented_unsafe_blocks", - "clippy::unimplemented", - "clippy::uninlined_format_args", - "clippy::unnecessary_box_returns", - "clippy::unnecessary_safety_comment", - "clippy::unnecessary_safety_doc", - "clippy::unnecessary_self_imports", - "clippy::unnecessary_struct_initialization", - "clippy::unnecessary_wraps", - "clippy::unnested_or_patterns", - "clippy::unused_peekable", - "clippy::unused_rounding", - "clippy::unused_self", - "clippy::use_self", - "clippy::useless_transmute", - "clippy::verbose_file_reads", - "clippy::wildcard_dependencies", - "clippy::wildcard_imports", - "clippy::zero_sized_map_values", - "elided_lifetimes_in_paths", - "future_incompatible", - "nonstandard_style", - "rust_2018_idioms", - "rust_2021_prelude_collisions", - "rustdoc::missing_crate_level_docs", - "semicolon_in_expressions_from_macros", - "trivial_numeric_casts", - "unsafe_op_in_unsafe_fn", # `unsafe_op_in_unsafe_fn` may become the default in future Rust versions: https://github.com/rust-lang/rust/issues/71668 - "unused_extern_crates", - "unused_import_braces", - "unused_lifetimes", - - - # Enable when we update MSRV: - # "clippy::implied_bounds_in_impls", - # "clippy::needless_pass_by_ref_mut", - # "clippy::readonly_write_lock", - # "clippy::should_panic_without_expect", - # "clippy::string_lit_chars_any", -] - -allow = [ - "clippy::manual_range_contains", # this one is just worse imho - "clippy::significant_drop_tightening", # A lot of false positives - - # TODO(emilk): enable more of these lints: - "clippy::cloned_instead_of_copied", - "clippy::let_underscore_untyped", - "clippy::missing_assert_message", - "clippy::missing_errors_doc", - "clippy::print_stderr", # TODO(emilk): use `log` crate instead - "clippy::self_named_module_files", # False positives - "clippy::too_many_lines", - "clippy::undocumented_unsafe_blocks", - "clippy::unwrap_used", - "clippy::useless_let_if_seq", # False positives - "clippy::wildcard_imports", # We do this a lot - "trivial_casts", - "unused_qualifications", -] diff --git a/bacon.toml b/bacon.toml deleted file mode 100644 index 63d72eeb055..00000000000 --- a/bacon.toml +++ /dev/null @@ -1,74 +0,0 @@ -# This is a configuration file for the bacon tool -# More info at https://github.com/Canop/bacon - -default_job = "cranky" - -[jobs] - -[jobs.cranky] -command = [ - "cargo", - "cranky", - "--all-targets", - "--all-features", - "--color=always", -] -need_stdout = false -watch = ["tests", "benches", "examples"] - -[jobs.wasm] -command = [ - "cargo", - "cranky", - "-p=egui_demo_app", - "--lib", - "--target=wasm32-unknown-unknown", - "--target-dir=target_wasm", - "--all-features", - "--color=always", -] -need_stdout = false -watch = ["tests", "benches", "examples"] - -[jobs.test] -command = ["cargo", "test", "--color=always"] -need_stdout = true -watch = ["tests"] - -[jobs.doc] -command = ["cargo", "doc", "--color=always", "--all-features", "--no-deps"] -need_stdout = false - -# if the doc compiles, then it opens in your browser and bacon switches -# to the previous job -[jobs.doc-open] -command = [ - "cargo", - "doc", - "--color=always", - "--all-features", - "--no-deps", - "--open", -] -need_stdout = false -on_success = "back" # so that we don't open the browser at each change - -# You can run your application and have the result displayed in bacon, -# *if* it makes sense for this crate. You can run an example the same -# way. Don't forget the `--color always` part or the errors won't be -# properly parsed. -[jobs.run] -command = ["cargo", "run", "--color=always"] -need_stdout = true - -# You may define here keybindings that would be specific to -# a project, for example a shortcut to launch a specific job. -# Shortcuts to internal functions (scrolling, toggling, etc.) -# should go in your personal prefs.toml file instead. -[keybindings] -i = "job:initial" -c = "job:cranky" -a = "job:wasm" -d = "job:doc-open" -t = "job:test" -r = "job:run" diff --git a/crates/ecolor/Cargo.toml b/crates/ecolor/Cargo.toml index 611b9a4dec9..ad5d6f96369 100644 --- a/crates/ecolor/Cargo.toml +++ b/crates/ecolor/Cargo.toml @@ -16,6 +16,9 @@ categories = ["mathematics", "encoding"] keywords = ["gui", "color", "conversion", "gamedev", "images"] include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"] +[lints] +workspace = true + [package.metadata.docs.rs] all-features = true diff --git a/crates/eframe/Cargo.toml b/crates/eframe/Cargo.toml index 5b2651c9ad4..c5cfae87733 100644 --- a/crates/eframe/Cargo.toml +++ b/crates/eframe/Cargo.toml @@ -24,6 +24,9 @@ all-features = true rustc-args = ["--cfg=web_sys_unstable_apis"] targets = ["x86_64-unknown-linux-gnu", "wasm32-unknown-unknown"] +[lints] +workspace = true + [lib] diff --git a/crates/eframe/src/native/app_icon.rs b/crates/eframe/src/native/app_icon.rs index 39c32dfdb3e..90100298680 100644 --- a/crates/eframe/src/native/app_icon.rs +++ b/crates/eframe/src/native/app_icon.rs @@ -224,6 +224,7 @@ fn set_title_and_icon_mac(title: &str, icon_data: Option<&IconData>) -> AppIconS static NSApp: Option<&'static NSApplication>; } + // SAFETY: we don't do anything dangerous here unsafe { let Some(app) = NSApp else { log::debug!("NSApp is null"); diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index 880b906c2b8..f08ed14d623 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -5,7 +5,11 @@ //! There is a bunch of improvements we could do, //! like removing a bunch of `unwraps`. -#![allow(clippy::arc_with_non_send_sync)] // glow::Context was accidentally non-Sync in glow 0.13, but that will be fixed in future releases of glow: https://github.com/grovesNL/glow/commit/c4a5f7151b9b4bbb380faa06ec27415235d1bf7e +// `clippy::arc_with_non_send_sync`: `glow::Context` was accidentally non-Sync in glow 0.13, +// but that will be fixed in future releases of glow. +// https://github.com/grovesNL/glow/commit/c4a5f7151b9b4bbb380faa06ec27415235d1bf7e +#![allow(clippy::arc_with_non_send_sync)] +#![allow(clippy::undocumented_unsafe_blocks)] use std::{cell::RefCell, num::NonZeroU32, rc::Rc, sync::Arc, time::Instant}; diff --git a/crates/eframe/src/web/text_agent.rs b/crates/eframe/src/web/text_agent.rs index c61b7093596..5cfec81bf30 100644 --- a/crates/eframe/src/web/text_agent.rs +++ b/crates/eframe/src/web/text_agent.rs @@ -119,7 +119,7 @@ pub fn install_text_agent(runner_ref: &WebRunner) -> Result<(), JsValue> { } /// Focus or blur text agent to toggle mobile keyboard. -pub fn update_text_agent(runner: &mut AppRunner) -> Option<()> { +pub fn update_text_agent(runner: &AppRunner) -> Option<()> { use web_sys::HtmlInputElement; let window = web_sys::window()?; let document = window.document()?; diff --git a/crates/egui-wgpu/Cargo.toml b/crates/egui-wgpu/Cargo.toml index 09e48e07643..817233b88ba 100644 --- a/crates/egui-wgpu/Cargo.toml +++ b/crates/egui-wgpu/Cargo.toml @@ -23,6 +23,9 @@ include = [ "Cargo.toml", ] +[lints] +workspace = true + [package.metadata.docs.rs] all-features = true diff --git a/crates/egui-wgpu/src/winit.rs b/crates/egui-wgpu/src/winit.rs index 07c917155aa..84dd0b41235 100644 --- a/crates/egui-wgpu/src/winit.rs +++ b/crates/egui-wgpu/src/winit.rs @@ -1,3 +1,6 @@ +#![allow(clippy::missing_errors_doc)] +#![allow(clippy::undocumented_unsafe_blocks)] + use std::{num::NonZeroU32, sync::Arc}; use egui::{ViewportId, ViewportIdMap, ViewportIdSet}; diff --git a/crates/egui-winit/Cargo.toml b/crates/egui-winit/Cargo.toml index a09829699fc..fc2b8a53620 100644 --- a/crates/egui-winit/Cargo.toml +++ b/crates/egui-winit/Cargo.toml @@ -13,6 +13,9 @@ categories = ["gui", "game-development"] keywords = ["winit", "egui", "gui", "gamedev"] include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"] +[lints] +workspace = true + [package.metadata.docs.rs] all-features = true diff --git a/crates/egui-winit/src/clipboard.rs b/crates/egui-winit/src/clipboard.rs index c86a9e19d5b..44e3840b64f 100644 --- a/crates/egui-winit/src/clipboard.rs +++ b/crates/egui-winit/src/clipboard.rs @@ -137,6 +137,8 @@ fn init_arboard() -> Option { fn init_smithay_clipboard( raw_display_handle: Option, ) -> Option { + #![allow(clippy::undocumented_unsafe_blocks)] + crate::profile_function!(); if let Some(RawDisplayHandle::Wayland(display)) = raw_display_handle { diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 313cc58906b..abadd9ee5c2 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -56,7 +56,7 @@ pub struct EventResponse { /// (e.g. a mouse click on an egui window, or entering text into a text field). /// /// For instance, if you use egui for a game, you should only - /// pass on the events to your game when [`Self::consumed`] is `false. + /// pass on the events to your game when [`Self::consumed`] is `false`. /// /// Note that egui uses `tab` to move focus between elements, so this will always be `true` for tabs. pub consumed: bool, @@ -1521,6 +1521,9 @@ fn process_viewport_command( /// Build and intitlaize a window. /// /// Wrapper around `create_winit_window_builder` and `apply_viewport_builder_to_window`. +/// +/// # Errors +/// Possible causes of error include denied permission, incompatible system, and lack of memory. pub fn create_window( egui_ctx: &egui::Context, event_loop: &EventLoopWindowTarget, diff --git a/crates/egui/Cargo.toml b/crates/egui/Cargo.toml index 87b25a7ea4b..a1929f060ad 100644 --- a/crates/egui/Cargo.toml +++ b/crates/egui/Cargo.toml @@ -13,6 +13,9 @@ categories = ["gui", "game-development"] keywords = ["gui", "imgui", "immediate", "portable", "gamedev"] include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"] +[lints] +workspace = true + [package.metadata.docs.rs] all-features = true diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index aa8a0b13e32..192b0cc49d4 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -1838,7 +1838,7 @@ impl Context { let paint_widget_id = |id: Id, text: &str, color: Color32| { if let Some(widget) = - self.write(|ctx| ctx.viewport().widgets_this_frame.get(id).cloned()) + self.write(|ctx| ctx.viewport().widgets_this_frame.get(id).copied()) { paint_widget(&widget, text, color); } @@ -2398,7 +2398,7 @@ impl Context { /// See also [`Response::contains_pointer`]. pub fn rect_contains_pointer(&self, layer_id: LayerId, rect: Rect) -> bool { let rect = - if let Some(transform) = self.memory(|m| m.layer_transforms.get(&layer_id).cloned()) { + if let Some(transform) = self.memory(|m| m.layer_transforms.get(&layer_id).copied()) { transform * rect } else { rect diff --git a/crates/egui/src/data/key.rs b/crates/egui/src/data/key.rs index 92e6b69dba2..66fe6b9336f 100644 --- a/crates/egui/src/data/key.rs +++ b/crates/egui/src/data/key.rs @@ -35,25 +35,25 @@ pub enum Key { /// `,` Comma, - /// '\\' + /// `\` Backslash, - /// '/' + /// `/` Slash, - /// '|', a vertical bar + /// `|`, a vertical bar Pipe, /// `?` Questionmark, - // '[' + // `[` OpenBracket, - // ']' + // `]` CloseBracket, - /// '`', also known as "backquote" or "grave" + /// \`, also known as "backquote" or "grave" Backtick, /// `-` diff --git a/crates/egui/src/load/bytes_loader.rs b/crates/egui/src/load/bytes_loader.rs index 3ab46794912..d03b2ad418a 100644 --- a/crates/egui/src/load/bytes_loader.rs +++ b/crates/egui/src/load/bytes_loader.rs @@ -53,7 +53,7 @@ impl BytesLoader for DefaultBytesLoader { #[cfg(feature = "log")] log::trace!("forget {uri:?}"); - let _ = self.cache.lock().remove(uri); + self.cache.lock().remove(uri); } fn forget_all(&self) { diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index 1bee1b29860..44eb4f74bd6 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -465,7 +465,7 @@ impl Response { let mut pos = self.ctx.input(|i| i.pointer.hover_pos())?; if let Some(transform) = self .ctx - .memory(|m| m.layer_transforms.get(&self.layer_id).cloned()) + .memory(|m| m.layer_transforms.get(&self.layer_id).copied()) { pos = transform * pos; } diff --git a/crates/egui/src/text_selection/visuals.rs b/crates/egui/src/text_selection/visuals.rs index 4fc8af0abd4..d31f1756edd 100644 --- a/crates/egui/src/text_selection/visuals.rs +++ b/crates/egui/src/text_selection/visuals.rs @@ -78,7 +78,7 @@ pub fn paint_cursor_end(painter: &Painter, visuals: &Visuals, cursor_rect: Rect) /// Paint one end of the selection, e.g. the primary cursor, with blinking (if enabled). pub fn paint_text_cursor( - ui: &mut Ui, + ui: &Ui, painter: &Painter, primary_cursor_rect: Rect, time_since_last_edit: f64, diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index c0b8532d6d9..440916f31b2 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -702,7 +702,7 @@ impl<'t> TextEdit<'t> { // Set IME output (in screen coords) when text is editable and visible let transform = ui - .memory(|m| m.layer_transforms.get(&ui.layer_id()).cloned()) + .memory(|m| m.layer_transforms.get(&ui.layer_id()).copied()) .unwrap_or_default(); ui.ctx().output_mut(|o| { @@ -948,7 +948,7 @@ fn events( key, pressed: true, .. - } => check_for_mutating_key_press(os, &mut cursor_range, text, galley, modifiers, *key), + } => check_for_mutating_key_press(os, &cursor_range, text, galley, modifiers, *key), Event::Ime(ime_event) => match ime_event { ImeEvent::Enabled => { @@ -1028,7 +1028,7 @@ fn events( /// Returns `Some(new_cursor)` if we did mutate `text`. fn check_for_mutating_key_press( os: OperatingSystem, - cursor_range: &mut CursorRange, + cursor_range: &CursorRange, text: &mut dyn TextBuffer, galley: &Galley, modifiers: &Modifiers, diff --git a/crates/egui_demo_app/Cargo.toml b/crates/egui_demo_app/Cargo.toml index b928702e08b..4ae9572bd0a 100644 --- a/crates/egui_demo_app/Cargo.toml +++ b/crates/egui_demo_app/Cargo.toml @@ -8,6 +8,9 @@ rust-version.workspace = true publish = false default-run = "egui_demo_app" +[lints] +workspace = true + [package.metadata.docs.rs] all-features = true diff --git a/crates/egui_demo_app/src/apps/custom3d_glow.rs b/crates/egui_demo_app/src/apps/custom3d_glow.rs index 7f488e7671e..ad6bfc3bc60 100644 --- a/crates/egui_demo_app/src/apps/custom3d_glow.rs +++ b/crates/egui_demo_app/src/apps/custom3d_glow.rs @@ -1,3 +1,5 @@ +#![allow(clippy::undocumented_unsafe_blocks)] + use std::sync::Arc; use eframe::egui_glow; diff --git a/crates/egui_demo_app/src/apps/http_app.rs b/crates/egui_demo_app/src/apps/http_app.rs index d0da5c0a395..d6b57284267 100644 --- a/crates/egui_demo_app/src/apps/http_app.rs +++ b/crates/egui_demo_app/src/apps/http_app.rs @@ -116,7 +116,7 @@ impl eframe::App for HttpApp { } } -fn ui_url(ui: &mut egui::Ui, frame: &mut eframe::Frame, url: &mut String) -> bool { +fn ui_url(ui: &mut egui::Ui, frame: &eframe::Frame, url: &mut String) -> bool { let mut trigger_fetch = false; ui.horizontal(|ui| { diff --git a/crates/egui_demo_app/src/main.rs b/crates/egui_demo_app/src/main.rs index 45b0ee067a4..9d660ed046c 100644 --- a/crates/egui_demo_app/src/main.rs +++ b/crates/egui_demo_app/src/main.rs @@ -1,6 +1,7 @@ //! Demo app for egui #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(rustdoc::missing_crate_level_docs)] // it's an example #![allow(clippy::never_loop)] // False positive // When compiling natively: diff --git a/crates/egui_demo_lib/Cargo.toml b/crates/egui_demo_lib/Cargo.toml index 8c5a703b5d6..d1daa5b8cfb 100644 --- a/crates/egui_demo_lib/Cargo.toml +++ b/crates/egui_demo_lib/Cargo.toml @@ -19,6 +19,9 @@ include = [ "data/icon.png", ] +[lints] +workspace = true + [package.metadata.docs.rs] all-features = true diff --git a/crates/egui_demo_lib/src/easy_mark/easy_mark_editor.rs b/crates/egui_demo_lib/src/easy_mark/easy_mark_editor.rs index 0b0bb9608a3..be2650cb03e 100644 --- a/crates/egui_demo_lib/src/easy_mark/easy_mark_editor.rs +++ b/crates/egui_demo_lib/src/easy_mark/easy_mark_editor.rs @@ -45,7 +45,7 @@ impl EasyMarkEditor { pub fn ui(&mut self, ui: &mut egui::Ui) { egui::Grid::new("controls").show(ui, |ui| { - let _ = ui.button("Hotkeys").on_hover_ui(nested_hotkeys_ui); + let _response = ui.button("Hotkeys").on_hover_ui(nested_hotkeys_ui); ui.checkbox(&mut self.show_rendered, "Show rendered"); ui.checkbox(&mut self.highlight_editor, "Highlight editor"); egui::reset_button(ui, self, "Reset"); diff --git a/crates/egui_extras/Cargo.toml b/crates/egui_extras/Cargo.toml index a0829180924..372f754cc52 100644 --- a/crates/egui_extras/Cargo.toml +++ b/crates/egui_extras/Cargo.toml @@ -17,6 +17,9 @@ categories = ["gui", "game-development"] keywords = ["gui", "imgui", "immediate", "portable", "gamedev"] include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"] +[lints] +workspace = true + [package.metadata.docs.rs] all-features = true diff --git a/crates/egui_glow/Cargo.toml b/crates/egui_glow/Cargo.toml index 1ee1f6cd90b..6bb081980d3 100644 --- a/crates/egui_glow/Cargo.toml +++ b/crates/egui_glow/Cargo.toml @@ -19,6 +19,9 @@ include = [ "src/shader/*.glsl", ] +[lints] +workspace = true + [package.metadata.docs.rs] all-features = true diff --git a/crates/egui_glow/examples/pure_glow.rs b/crates/egui_glow/examples/pure_glow.rs index 3da382af6ec..70f07421475 100644 --- a/crates/egui_glow/examples/pure_glow.rs +++ b/crates/egui_glow/examples/pure_glow.rs @@ -1,8 +1,11 @@ //! Example how to use pure `egui_glow`. #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(rustdoc::missing_crate_level_docs)] // it's an example +#![allow(clippy::undocumented_unsafe_blocks)] +#![allow(clippy::arc_with_non_send_sync)] +// `clippy::arc_with_non_send_sync`: `glow::Context` was accidentally non-Sync in glow 0.13, but that will be fixed in future releases of glow: https://github.com/grovesNL/glow/commit/c4a5f7151b9b4bbb380faa06ec27415235d1bf7e #![allow(unsafe_code)] -#![allow(clippy::arc_with_non_send_sync)] // glow::Context was accidentally non-Sync in glow 0.13, but that will be fixed in future releases of glow: https://github.com/grovesNL/glow/commit/c4a5f7151b9b4bbb380faa06ec27415235d1bf7e use std::num::NonZeroU32; diff --git a/crates/egui_glow/src/lib.rs b/crates/egui_glow/src/lib.rs index b12d2ff5f61..8621dbd74e7 100644 --- a/crates/egui_glow/src/lib.rs +++ b/crates/egui_glow/src/lib.rs @@ -10,6 +10,7 @@ #![allow(clippy::float_cmp)] #![allow(clippy::manual_range_contains)] +#![allow(clippy::undocumented_unsafe_blocks)] pub mod painter; pub use glow; diff --git a/crates/egui_glow/src/shader_version.rs b/crates/egui_glow/src/shader_version.rs index c59792a0aba..77c1e63cea9 100644 --- a/crates/egui_glow/src/shader_version.rs +++ b/crates/egui_glow/src/shader_version.rs @@ -1,4 +1,5 @@ #![allow(unsafe_code)] +#![allow(clippy::undocumented_unsafe_blocks)] use std::convert::TryInto; diff --git a/crates/egui_plot/Cargo.toml b/crates/egui_plot/Cargo.toml index 61faf7432c0..91c4b3a50fc 100644 --- a/crates/egui_plot/Cargo.toml +++ b/crates/egui_plot/Cargo.toml @@ -17,6 +17,9 @@ categories = ["visualization", "gui"] keywords = ["egui", "plot", "plotting"] include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"] +[lints] +workspace = true + [package.metadata.docs.rs] all-features = true diff --git a/crates/emath/Cargo.toml b/crates/emath/Cargo.toml index a48a9cbdbaf..ae0153cc9f2 100644 --- a/crates/emath/Cargo.toml +++ b/crates/emath/Cargo.toml @@ -13,6 +13,9 @@ categories = ["mathematics", "gui"] keywords = ["math", "gui"] include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"] +[lints] +workspace = true + [package.metadata.docs.rs] all-features = true diff --git a/crates/epaint/Cargo.toml b/crates/epaint/Cargo.toml index a24a299b8ed..861afb1a14b 100644 --- a/crates/epaint/Cargo.toml +++ b/crates/epaint/Cargo.toml @@ -22,6 +22,9 @@ include = [ "fonts/UFL.txt", ] +[lints] +workspace = true + [package.metadata.docs.rs] all-features = true diff --git a/examples/confirm_exit/Cargo.toml b/examples/confirm_exit/Cargo.toml index 8e9d3af2872..b1cab21a1bb 100644 --- a/examples/confirm_exit/Cargo.toml +++ b/examples/confirm_exit/Cargo.toml @@ -7,6 +7,9 @@ edition = "2021" rust-version = "1.76" publish = false +[lints] +workspace = true + [dependencies] eframe = { workspace = true, features = [ diff --git a/examples/confirm_exit/src/main.rs b/examples/confirm_exit/src/main.rs index 3a09da59d91..14816e1bdea 100644 --- a/examples/confirm_exit/src/main.rs +++ b/examples/confirm_exit/src/main.rs @@ -1,4 +1,5 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(rustdoc::missing_crate_level_docs)] // it's an example use eframe::egui; diff --git a/examples/custom_3d_glow/Cargo.toml b/examples/custom_3d_glow/Cargo.toml index ef3d1cbc828..3b8c9a45383 100644 --- a/examples/custom_3d_glow/Cargo.toml +++ b/examples/custom_3d_glow/Cargo.toml @@ -7,6 +7,9 @@ edition = "2021" rust-version = "1.76" publish = false +[lints] +workspace = true + [dependencies] eframe = { workspace = true, features = [ diff --git a/examples/custom_3d_glow/src/main.rs b/examples/custom_3d_glow/src/main.rs index a1f6fa26946..241124b7c17 100644 --- a/examples/custom_3d_glow/src/main.rs +++ b/examples/custom_3d_glow/src/main.rs @@ -1,5 +1,7 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(rustdoc::missing_crate_level_docs)] // it's an example #![allow(unsafe_code)] +#![allow(clippy::undocumented_unsafe_blocks)] use eframe::{egui, egui_glow, glow}; diff --git a/examples/custom_font/Cargo.toml b/examples/custom_font/Cargo.toml index 89840c4526d..d6021c12425 100644 --- a/examples/custom_font/Cargo.toml +++ b/examples/custom_font/Cargo.toml @@ -7,6 +7,9 @@ edition = "2021" rust-version = "1.76" publish = false +[lints] +workspace = true + [dependencies] eframe = { workspace = true, features = [ diff --git a/examples/custom_font/src/main.rs b/examples/custom_font/src/main.rs index 8a852319dd1..f42c5763150 100644 --- a/examples/custom_font/src/main.rs +++ b/examples/custom_font/src/main.rs @@ -1,4 +1,5 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(rustdoc::missing_crate_level_docs)] // it's an example use eframe::egui; diff --git a/examples/custom_font_style/Cargo.toml b/examples/custom_font_style/Cargo.toml index 60e3e37b9c2..65b5045238c 100644 --- a/examples/custom_font_style/Cargo.toml +++ b/examples/custom_font_style/Cargo.toml @@ -7,6 +7,9 @@ edition = "2021" rust-version = "1.76" publish = false +[lints] +workspace = true + [dependencies] eframe = { workspace = true, features = [ diff --git a/examples/custom_font_style/src/main.rs b/examples/custom_font_style/src/main.rs index 89117b4f01a..c1a61e3e69c 100644 --- a/examples/custom_font_style/src/main.rs +++ b/examples/custom_font_style/src/main.rs @@ -1,4 +1,5 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(rustdoc::missing_crate_level_docs)] // it's an example use eframe::egui; use egui::{FontFamily, FontId, RichText, TextStyle}; diff --git a/examples/custom_keypad/Cargo.toml b/examples/custom_keypad/Cargo.toml index f008884c5c7..7d000fd0d46 100644 --- a/examples/custom_keypad/Cargo.toml +++ b/examples/custom_keypad/Cargo.toml @@ -7,6 +7,9 @@ edition = "2021" rust-version = "1.76" publish = false +[lints] +workspace = true + [dependencies] eframe = { workspace = true, features = [ diff --git a/examples/custom_keypad/src/main.rs b/examples/custom_keypad/src/main.rs index 5cb26240cbd..654de25fa44 100644 --- a/examples/custom_keypad/src/main.rs +++ b/examples/custom_keypad/src/main.rs @@ -1,4 +1,5 @@ // #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(rustdoc::missing_crate_level_docs)] // it's an example use eframe::egui; mod keypad; diff --git a/examples/custom_plot_manipulation/Cargo.toml b/examples/custom_plot_manipulation/Cargo.toml index 3db38a45b92..fc7b66c5ccc 100644 --- a/examples/custom_plot_manipulation/Cargo.toml +++ b/examples/custom_plot_manipulation/Cargo.toml @@ -7,6 +7,9 @@ edition = "2021" rust-version = "1.76" publish = false +[lints] +workspace = true + [dependencies] eframe = { workspace = true, features = [ diff --git a/examples/custom_plot_manipulation/src/main.rs b/examples/custom_plot_manipulation/src/main.rs index 4dffaf18ed1..e423d890fcb 100644 --- a/examples/custom_plot_manipulation/src/main.rs +++ b/examples/custom_plot_manipulation/src/main.rs @@ -1,5 +1,6 @@ //! This example shows how to implement custom gestures to pan and zoom in the plot #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(rustdoc::missing_crate_level_docs)] // it's an example use eframe::egui::{self, DragValue, Event, Vec2}; use egui_plot::{Legend, Line, PlotPoints}; diff --git a/examples/custom_window_frame/Cargo.toml b/examples/custom_window_frame/Cargo.toml index c334c6a662b..6b800d0a0af 100644 --- a/examples/custom_window_frame/Cargo.toml +++ b/examples/custom_window_frame/Cargo.toml @@ -7,6 +7,9 @@ edition = "2021" rust-version = "1.76" publish = false +[lints] +workspace = true + [dependencies] eframe = { workspace = true, features = [ diff --git a/examples/custom_window_frame/src/main.rs b/examples/custom_window_frame/src/main.rs index 87daf82e06a..bb098652cbf 100644 --- a/examples/custom_window_frame/src/main.rs +++ b/examples/custom_window_frame/src/main.rs @@ -1,6 +1,7 @@ //! Show a custom window frame instead of the default OS window chrome decorations. #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(rustdoc::missing_crate_level_docs)] // it's an example use eframe::egui::{self, ViewportCommand}; diff --git a/examples/file_dialog/Cargo.toml b/examples/file_dialog/Cargo.toml index ef30dfce8e1..9684a423256 100644 --- a/examples/file_dialog/Cargo.toml +++ b/examples/file_dialog/Cargo.toml @@ -7,6 +7,9 @@ edition = "2021" rust-version = "1.76" publish = false +[lints] +workspace = true + [dependencies] eframe = { workspace = true, features = [ diff --git a/examples/file_dialog/src/main.rs b/examples/file_dialog/src/main.rs index 4077c6de4a2..d0623a73a28 100644 --- a/examples/file_dialog/src/main.rs +++ b/examples/file_dialog/src/main.rs @@ -1,4 +1,5 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(rustdoc::missing_crate_level_docs)] // it's an example use eframe::egui; diff --git a/examples/hello_world/Cargo.toml b/examples/hello_world/Cargo.toml index acf7e2b8ab8..bdd728a61e1 100644 --- a/examples/hello_world/Cargo.toml +++ b/examples/hello_world/Cargo.toml @@ -7,6 +7,9 @@ edition = "2021" rust-version = "1.76" publish = false +[lints] +workspace = true + [dependencies] eframe = { workspace = true, features = [ diff --git a/examples/hello_world/src/main.rs b/examples/hello_world/src/main.rs index b3fda5a5810..1c74f4c4992 100644 --- a/examples/hello_world/src/main.rs +++ b/examples/hello_world/src/main.rs @@ -1,4 +1,5 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(rustdoc::missing_crate_level_docs)] // it's an example use eframe::egui; diff --git a/examples/hello_world_par/Cargo.toml b/examples/hello_world_par/Cargo.toml index 3d0e92eaec4..e64cdd36108 100644 --- a/examples/hello_world_par/Cargo.toml +++ b/examples/hello_world_par/Cargo.toml @@ -7,6 +7,9 @@ edition = "2021" rust-version = "1.76" publish = false +[lints] +workspace = true + [dependencies] eframe = { workspace = true, default-features = false, features = [ diff --git a/examples/hello_world_par/src/main.rs b/examples/hello_world_par/src/main.rs index 617e840dc4c..2896f24843f 100644 --- a/examples/hello_world_par/src/main.rs +++ b/examples/hello_world_par/src/main.rs @@ -1,6 +1,7 @@ //! This example shows that you can use egui in parallel from multiple threads. #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(rustdoc::missing_crate_level_docs)] // it's an example use std::sync::mpsc; use std::thread::JoinHandle; diff --git a/examples/hello_world_simple/Cargo.toml b/examples/hello_world_simple/Cargo.toml index bec4670b790..c88382b9f6d 100644 --- a/examples/hello_world_simple/Cargo.toml +++ b/examples/hello_world_simple/Cargo.toml @@ -7,6 +7,9 @@ edition = "2021" rust-version = "1.76" publish = false +[lints] +workspace = true + [dependencies] eframe = { workspace = true, features = [ diff --git a/examples/hello_world_simple/src/main.rs b/examples/hello_world_simple/src/main.rs index 5f0ed31a49f..4e48feeff90 100644 --- a/examples/hello_world_simple/src/main.rs +++ b/examples/hello_world_simple/src/main.rs @@ -1,4 +1,5 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(rustdoc::missing_crate_level_docs)] // it's an example use eframe::egui; diff --git a/examples/images/Cargo.toml b/examples/images/Cargo.toml index cd9157f5715..092535c9125 100644 --- a/examples/images/Cargo.toml +++ b/examples/images/Cargo.toml @@ -7,6 +7,9 @@ edition = "2021" rust-version = "1.76" publish = false +[lints] +workspace = true + [dependencies] eframe = { workspace = true, features = [ diff --git a/examples/images/src/main.rs b/examples/images/src/main.rs index efb088b6727..8a124e051a5 100644 --- a/examples/images/src/main.rs +++ b/examples/images/src/main.rs @@ -1,4 +1,5 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(rustdoc::missing_crate_level_docs)] // it's an example use eframe::egui; diff --git a/examples/keyboard_events/Cargo.toml b/examples/keyboard_events/Cargo.toml index 08f7ba3a81e..f7b12047062 100644 --- a/examples/keyboard_events/Cargo.toml +++ b/examples/keyboard_events/Cargo.toml @@ -7,6 +7,9 @@ edition = "2021" rust-version = "1.76" publish = false +[lints] +workspace = true + [dependencies] eframe = { workspace = true, features = [ diff --git a/examples/keyboard_events/src/main.rs b/examples/keyboard_events/src/main.rs index 581ae11eb74..bf0b3389981 100644 --- a/examples/keyboard_events/src/main.rs +++ b/examples/keyboard_events/src/main.rs @@ -1,4 +1,5 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(rustdoc::missing_crate_level_docs)] // it's an example use eframe::egui; use egui::*; diff --git a/examples/multiple_viewports/Cargo.toml b/examples/multiple_viewports/Cargo.toml index b5bd8e80411..7aad28550fc 100644 --- a/examples/multiple_viewports/Cargo.toml +++ b/examples/multiple_viewports/Cargo.toml @@ -7,6 +7,9 @@ edition = "2021" rust-version = "1.76" publish = false +[lints] +workspace = true + [features] wgpu = ["eframe/wgpu"] diff --git a/examples/multiple_viewports/src/main.rs b/examples/multiple_viewports/src/main.rs index 0ef06e7815f..262002e2c5b 100644 --- a/examples/multiple_viewports/src/main.rs +++ b/examples/multiple_viewports/src/main.rs @@ -1,4 +1,5 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(rustdoc::missing_crate_level_docs)] // it's an example use std::sync::{ atomic::{AtomicBool, Ordering}, diff --git a/examples/puffin_profiler/Cargo.toml b/examples/puffin_profiler/Cargo.toml index 6b6acad726d..d787eeb9168 100644 --- a/examples/puffin_profiler/Cargo.toml +++ b/examples/puffin_profiler/Cargo.toml @@ -7,6 +7,9 @@ edition = "2021" rust-version = "1.76" publish = false +[lints] +workspace = true + [features] wgpu = ["eframe/wgpu"] diff --git a/examples/puffin_profiler/src/main.rs b/examples/puffin_profiler/src/main.rs index 521184f506b..8fe30d679ff 100644 --- a/examples/puffin_profiler/src/main.rs +++ b/examples/puffin_profiler/src/main.rs @@ -1,4 +1,5 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(rustdoc::missing_crate_level_docs)] // it's an example use std::sync::{ atomic::{AtomicBool, Ordering}, diff --git a/examples/save_plot/Cargo.toml b/examples/save_plot/Cargo.toml index 7c9eb245285..c4c287c001d 100644 --- a/examples/save_plot/Cargo.toml +++ b/examples/save_plot/Cargo.toml @@ -7,6 +7,9 @@ edition = "2021" rust-version = "1.76" publish = false +[lints] +workspace = true + [dependencies] eframe = { workspace = true, features = [ "default", diff --git a/examples/save_plot/src/main.rs b/examples/save_plot/src/main.rs index 3f13d3f28b6..61657faae86 100644 --- a/examples/save_plot/src/main.rs +++ b/examples/save_plot/src/main.rs @@ -1,4 +1,5 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(rustdoc::missing_crate_level_docs)] // it's an example use eframe::egui; use egui_plot::{Legend, Line, Plot, PlotPoints}; diff --git a/examples/screenshot/Cargo.toml b/examples/screenshot/Cargo.toml index 0fb5da7fd2a..aba066669c9 100644 --- a/examples/screenshot/Cargo.toml +++ b/examples/screenshot/Cargo.toml @@ -10,6 +10,9 @@ edition = "2021" rust-version = "1.76" publish = false +[lints] +workspace = true + [dependencies] eframe = { workspace = true, features = [ diff --git a/examples/screenshot/src/main.rs b/examples/screenshot/src/main.rs index 91f2f92cfc0..c0c627e86a7 100644 --- a/examples/screenshot/src/main.rs +++ b/examples/screenshot/src/main.rs @@ -1,4 +1,5 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(rustdoc::missing_crate_level_docs)] // it's an example use std::sync::Arc; diff --git a/examples/serial_windows/Cargo.toml b/examples/serial_windows/Cargo.toml index 58d5cdecced..eebd08957af 100644 --- a/examples/serial_windows/Cargo.toml +++ b/examples/serial_windows/Cargo.toml @@ -7,6 +7,9 @@ edition = "2021" rust-version = "1.76" publish = false +[lints] +workspace = true + [dependencies] eframe = { workspace = true, features = [ diff --git a/examples/serial_windows/src/main.rs b/examples/serial_windows/src/main.rs index 57dccaa8b0e..04bed8006ae 100644 --- a/examples/serial_windows/src/main.rs +++ b/examples/serial_windows/src/main.rs @@ -1,4 +1,5 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(rustdoc::missing_crate_level_docs)] // it's an example use eframe::egui; diff --git a/examples/test_inline_glow_paint/Cargo.toml b/examples/test_inline_glow_paint/Cargo.toml index 893b0d07de1..ae66b6abdd3 100644 --- a/examples/test_inline_glow_paint/Cargo.toml +++ b/examples/test_inline_glow_paint/Cargo.toml @@ -7,6 +7,9 @@ edition = "2021" rust-version = "1.76" publish = false +[lints] +workspace = true + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] diff --git a/examples/test_inline_glow_paint/src/main.rs b/examples/test_inline_glow_paint/src/main.rs index 1710ee154a6..7851ad63b8b 100644 --- a/examples/test_inline_glow_paint/src/main.rs +++ b/examples/test_inline_glow_paint/src/main.rs @@ -1,3 +1,7 @@ +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(rustdoc::missing_crate_level_docs)] // it's an example +#![allow(clippy::undocumented_unsafe_blocks)] + // Test that we can paint to the screen using glow directly. use eframe::egui; diff --git a/examples/test_viewports/Cargo.toml b/examples/test_viewports/Cargo.toml index 4e106e37ec8..142bafe2293 100644 --- a/examples/test_viewports/Cargo.toml +++ b/examples/test_viewports/Cargo.toml @@ -7,6 +7,9 @@ edition = "2021" rust-version = "1.76" publish = false +[lints] +workspace = true + [features] wgpu = ["eframe/wgpu"] diff --git a/examples/test_viewports/src/main.rs b/examples/test_viewports/src/main.rs index 05679668013..8bbb1b22a07 100644 --- a/examples/test_viewports/src/main.rs +++ b/examples/test_viewports/src/main.rs @@ -1,3 +1,6 @@ +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(rustdoc::missing_crate_level_docs)] // it's an example + use std::sync::Arc; use eframe::egui; diff --git a/examples/user_attention/Cargo.toml b/examples/user_attention/Cargo.toml index 10f0ce711c0..6c3c91cdc25 100644 --- a/examples/user_attention/Cargo.toml +++ b/examples/user_attention/Cargo.toml @@ -7,6 +7,9 @@ edition = "2021" rust-version = "1.76" publish = false +[lints] +workspace = true + [dependencies] eframe = { workspace = true, features = [ "default", diff --git a/examples/user_attention/src/main.rs b/examples/user_attention/src/main.rs index 4625abefa93..cbe27b6d5bc 100644 --- a/examples/user_attention/src/main.rs +++ b/examples/user_attention/src/main.rs @@ -1,3 +1,6 @@ +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(rustdoc::missing_crate_level_docs)] // it's an example + use eframe::{egui, CreationContext, NativeOptions}; use egui::{Button, CentralPanel, Context, UserAttentionType}; diff --git a/scripts/check.sh b/scripts/check.sh index c1852a2d81a..5b802f6c606 100755 --- a/scripts/check.sh +++ b/scripts/check.sh @@ -9,7 +9,6 @@ set -x # Checks all tests, lints etc. # Basically does what the CI does. -cargo install --quiet cargo-cranky # Uses lints defined in Cranky.toml. See https://github.com/ericseppanen/cargo-cranky cargo +1.75.0 install --quiet typos-cli # web_sys_unstable_apis is required to enable the web_sys clipboard API which eframe web uses, @@ -24,7 +23,7 @@ cargo fmt --all -- --check cargo doc --quiet --lib --no-deps --all-features cargo doc --quiet --document-private-items --no-deps --all-features -cargo cranky --quiet --all-targets --all-features -- -D warnings +cargo clippy --quiet --all-targets --all-features -- -D warnings ./scripts/clippy_wasm.sh cargo check --quiet --all-targets diff --git a/scripts/clippy_wasm.sh b/scripts/clippy_wasm.sh index 72f6cc0e53e..3d80bd0f703 100755 --- a/scripts/clippy_wasm.sh +++ b/scripts/clippy_wasm.sh @@ -10,4 +10,4 @@ set -x # Use scripts/clippy_wasm/clippy.toml export CLIPPY_CONF_DIR="scripts/clippy_wasm" -cargo cranky --quiet --all-features --target wasm32-unknown-unknown --target-dir target_wasm -p egui_demo_app --lib -- --deny warnings +cargo clippy --quiet --all-features --target wasm32-unknown-unknown --target-dir target_wasm -p egui_demo_app --lib -- --deny warnings diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index ba5b9382089..26bdbb47e59 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -6,4 +6,7 @@ rust-version.workspace = true version.workspace = true publish = false +[lints] +workspace = true + [dependencies] diff --git a/xtask/src/main.rs b/xtask/src/main.rs index b194629203a..0f16c545427 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -1,3 +1,5 @@ +//! Helper crate for running scripts within the `egui` repo + #![allow(clippy::print_stdout)] #![allow(clippy::print_stderr)] #![allow(clippy::exit)] From 0bc59f578b907e357f118dba362d89d5e36f4c31 Mon Sep 17 00:00:00 2001 From: YgorSouza <43298013+YgorSouza@users.noreply.github.com> Date: Fri, 26 Apr 2024 07:46:25 +0200 Subject: [PATCH 114/134] Fix some typos from the cranky-clippy transition (#4417) --- .vscode/settings.json | 4 ++-- Cargo.toml | 4 ++-- clippy.toml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 8ec1e4aa67f..681dc12fe15 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -15,7 +15,7 @@ // Tell Rust Analyzer to use its own target directory, so we don't need to wait for it to finish wen we want to `cargo run` "rust-analyzer.check.overrideCommand": [ "cargo", - "cranky", + "clippy", "--target-dir=target_ra", "--workspace", "--message-format=json", @@ -24,7 +24,7 @@ ], "rust-analyzer.cargo.buildScripts.overrideCommand": [ "cargo", - "cranky", + "clippy", "--quiet", "--target-dir=target_ra", "--workspace", diff --git a/Cargo.toml b/Cargo.toml index c94baab2843..0fd46f9ec0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -237,11 +237,11 @@ verbose_file_reads = "warn" wildcard_dependencies = "warn" zero_sized_map_values = "warn" -# TODO(emilk): enable more of these linits: +# TODO(emilk): enable more of these lints: iter_over_hash_type = "allow" let_underscore_untyped = "allow" missing_assert_message = "allow" -print_stderr = "allow" # TODO(emilk): use `log` crate insteaditer_over_hash_type = "allow" +print_stderr = "allow" # TODO(emilk): use `log` crate instead should_panic_without_expect = "allow" too_many_lines = "allow" unwrap_used = "allow" # TODO(emilk): We really wanna warn on this one diff --git a/clippy.toml b/clippy.toml index d91f9666c47..f480ca93d7e 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1,4 +1,4 @@ -# There is also a scripts/clippy_wasm/clippy.toml which forbids some mthods that are not available in wasm. +# There is also a scripts/clippy_wasm/clippy.toml which forbids some methods that are not available in wasm. # ----------------------------------------------------------------------------- # Section identical to scripts/clippy_wasm/clippy.toml: @@ -67,7 +67,7 @@ disallowed-types = [ # ----------------------------------------------------------------------------- -# Allow-list of words for markdown in dosctrings https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown +# Allow-list of words for markdown in docstrings https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown doc-valid-idents = [ # You must also update the same list in the root `clippy.toml`! "AccessKit", From c1fc9213c3fe9e1a034e42515b5db647cd2313b0 Mon Sep 17 00:00:00 2001 From: Simon Frankau Date: Mon, 29 Apr 2024 09:33:23 +0100 Subject: [PATCH 115/134] Enable egui_glow's winit feature on wasm (#4420) (#4421) Reverts change in #1303, enabling the egui_glow::winit module when using wasm. * Closes --- crates/egui_glow/Cargo.toml | 2 +- crates/egui_glow/src/lib.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/egui_glow/Cargo.toml b/crates/egui_glow/Cargo.toml index 6bb081980d3..44939640cfb 100644 --- a/crates/egui_glow/Cargo.toml +++ b/crates/egui_glow/Cargo.toml @@ -54,6 +54,7 @@ x11 = ["winit?/x11"] [dependencies] egui = { workspace = true, default-features = false, features = ["bytemuck"] } +egui-winit = { workspace = true, optional = true, default-features = false } bytemuck = "1.7" glow.workspace = true @@ -66,7 +67,6 @@ document-features = { workspace = true, optional = true } # Native: [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -egui-winit = { workspace = true, optional = true, default-features = false } puffin = { workspace = true, optional = true } winit = { workspace = true, optional = true, default-features = false, features = [ "rwh_06", # for compatibility with egui-winit diff --git a/crates/egui_glow/src/lib.rs b/crates/egui_glow/src/lib.rs index 8621dbd74e7..cfb95acd3d7 100644 --- a/crates/egui_glow/src/lib.rs +++ b/crates/egui_glow/src/lib.rs @@ -21,9 +21,9 @@ mod vao; pub use shader_version::ShaderVersion; -#[cfg(all(not(target_arch = "wasm32"), feature = "winit"))] +#[cfg(feature = "winit")] pub mod winit; -#[cfg(all(not(target_arch = "wasm32"), feature = "winit"))] +#[cfg(feature = "winit")] pub use winit::*; /// Check for OpenGL error and report it using `log::error`. From 3bb33980a93547c41898a2f0ba294843adaacea7 Mon Sep 17 00:00:00 2001 From: hardlydearly <167623323+hardlydearly@users.noreply.github.com> Date: Mon, 29 Apr 2024 16:33:29 +0800 Subject: [PATCH 116/134] chore: remove repetitive words (#4400) remove repetitive words Signed-off-by: hardlydearly <799511800@qq.com> --- crates/egui/src/callstack.rs | 2 +- crates/egui/src/containers/frame.rs | 2 +- crates/egui/src/context.rs | 4 ++-- crates/egui/src/memory.rs | 4 ++-- crates/egui/src/text_selection/cursor_range.rs | 2 +- crates/egui/src/text_selection/text_cursor_state.rs | 2 +- crates/egui/src/ui.rs | 4 ++-- crates/egui/src/viewport.rs | 4 ++-- crates/egui/src/widgets/text_edit/state.rs | 2 +- crates/epaint/src/tessellator.rs | 2 +- 10 files changed, 14 insertions(+), 14 deletions(-) diff --git a/crates/egui/src/callstack.rs b/crates/egui/src/callstack.rs index fac5ac5936e..aa6f44cb08d 100644 --- a/crates/egui/src/callstack.rs +++ b/crates/egui/src/callstack.rs @@ -77,7 +77,7 @@ pub fn capture() -> String { // Remove stuff that isn't user calls: let skip_prefixes = [ - // "backtrace::", // not needed, since we cut at at egui::callstack::capture + // "backtrace::", // not needed, since we cut at egui::callstack::capture "egui::", "", diff --git a/crates/egui/src/containers/frame.rs b/crates/egui/src/containers/frame.rs index 82ef47770a7..40bd17d3c4d 100644 --- a/crates/egui/src/containers/frame.rs +++ b/crates/egui/src/containers/frame.rs @@ -306,7 +306,7 @@ impl Prepared { self.content_ui.min_rect() + self.frame.inner_margin + self.frame.outer_margin } - /// Allocate the the space that was used by [`Self::content_ui`]. + /// Allocate the space that was used by [`Self::content_ui`]. /// /// This MUST be called, or the parent ui will not know how much space this widget used. /// diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 192b0cc49d4..f697fb4b8e5 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -257,7 +257,7 @@ pub struct RepaintCause { /// What file had the call that requested the repaint? pub file: &'static str, - /// What line number of the the call that requested the repaint? + /// What line number of the call that requested the repaint? pub line: u32, } @@ -924,7 +924,7 @@ impl Context { self.write(move |ctx| writer(&mut ctx.memory.options.tessellation_options)) } - /// If the given [`Id`] has been used previously the same frame at at different position, + /// If the given [`Id`] has been used previously the same frame at different position, /// then an error will be printed on screen. /// /// This function is already called for all widgets that do any interaction, diff --git a/crates/egui/src/memory.rs b/crates/egui/src/memory.rs index 2ef8bbf58ba..4bee395c530 100644 --- a/crates/egui/src/memory.rs +++ b/crates/egui/src/memory.rs @@ -132,7 +132,7 @@ enum FocusDirection { /// Select the widget below the current focused widget. Down, - /// Select the widget to the left of the the current focused widget. + /// Select the widget to the left of the current focused widget. Left, /// Select the previous widget that had focus. @@ -812,7 +812,7 @@ impl Memory { /// ## Popups /// Popups are things like combo-boxes, color pickers, menus etc. -/// Only one can be be open at a time. +/// Only one can be open at a time. impl Memory { /// Is the given popup open? pub fn is_popup_open(&self, popup_id: Id) -> bool { diff --git a/crates/egui/src/text_selection/cursor_range.rs b/crates/egui/src/text_selection/cursor_range.rs index cebcf3fb56d..cf17a2e9ba1 100644 --- a/crates/egui/src/text_selection/cursor_range.rs +++ b/crates/egui/src/text_selection/cursor_range.rs @@ -63,7 +63,7 @@ impl CursorRange { self.primary.ccursor == self.secondary.ccursor } - /// Is `self` a super-set of the the other range? + /// Is `self` a super-set of the other range? pub fn contains(&self, other: &Self) -> bool { let [self_min, self_max] = self.sorted_cursors(); let [other_min, other_max] = other.sorted_cursors(); diff --git a/crates/egui/src/text_selection/text_cursor_state.rs b/crates/egui/src/text_selection/text_cursor_state.rs index 7aca96f8b19..bd596e7e846 100644 --- a/crates/egui/src/text_selection/text_cursor_state.rs +++ b/crates/egui/src/text_selection/text_cursor_state.rs @@ -46,7 +46,7 @@ impl TextCursorState { self.cursor_range.is_none() && self.ccursor_range.is_none() } - /// The the currently selected range of characters. + /// The currently selected range of characters. pub fn char_range(&self) -> Option { self.ccursor_range.or_else(|| { self.cursor_range diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index d160f5d404d..1215d0eeb0f 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -1886,7 +1886,7 @@ impl Ui { /// adjusted up and down to lie in the center of the horizontal layout. /// The initial height is `style.spacing.interact_size.y`. /// Centering is almost always what you want if you are - /// planning to to mix widgets or use different types of text. + /// planning to mix widgets or use different types of text. /// /// If you don't want the contents to be centered, use [`Self::horizontal_top`] instead. /// @@ -1947,7 +1947,7 @@ impl Ui { /// adjusted up and down to lie in the center of the horizontal layout. /// The initial height is `style.spacing.interact_size.y`. /// Centering is almost always what you want if you are - /// planning to to mix widgets or use different types of text. + /// planning to mix widgets or use different types of text. /// /// The returned [`Response`] will only have checked for mouse hover /// but can be used for tooltips (`on_hover_text`). diff --git a/crates/egui/src/viewport.rs b/crates/egui/src/viewport.rs index e1e3ebf0e82..cce90487721 100644 --- a/crates/egui/src/viewport.rs +++ b/crates/egui/src/viewport.rs @@ -889,7 +889,7 @@ pub enum X11WindowType { /// This property is typically used on override-redirect windows. Combo, - /// This indicates the the window is being dragged. + /// This indicates the window is being dragged. /// This property is typically used on override-redirect windows. Dnd, } @@ -1015,7 +1015,7 @@ pub enum ViewportCommand { /// Set window to be always-on-top, always-on-bottom, or neither. WindowLevel(WindowLevel), - /// The the window icon. + /// The window icon. Icon(Option>), /// Set the IME cursor editing area. diff --git a/crates/egui/src/widgets/text_edit/state.rs b/crates/egui/src/widgets/text_edit/state.rs index ef4a8909a73..c95d676910a 100644 --- a/crates/egui/src/widgets/text_edit/state.rs +++ b/crates/egui/src/widgets/text_edit/state.rs @@ -67,7 +67,7 @@ impl TextEditState { ctx.data_mut(|d| d.insert_persisted(id, self)); } - /// The the currently selected range of characters. + /// The currently selected range of characters. #[deprecated = "Use `self.cursor.char_range` instead"] pub fn ccursor_range(&self) -> Option { self.cursor.char_range() diff --git a/crates/epaint/src/tessellator.rs b/crates/epaint/src/tessellator.rs index c7dc31b75ab..4135cc58d22 100644 --- a/crates/epaint/src/tessellator.rs +++ b/crates/epaint/src/tessellator.rs @@ -653,7 +653,7 @@ pub struct TessellationOptions { /// Default: `true`. pub feathering: bool, - /// The size of the the feathering, in physical pixels. + /// The size of the feathering, in physical pixels. /// /// The default, and suggested, value for this is `1.0`. /// If you use a larger value, edges will appear blurry. From af39bd22ab3c7f995b7341745ccb99570a094de3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20Jos=C3=A9=20Pereira?= Date: Mon, 29 Apr 2024 06:16:55 -0300 Subject: [PATCH 117/134] crates: egui_demo_lib: Fix table height (#4422) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Closes #4404 Signed-off-by: Patrick José Pereira --- crates/egui_demo_lib/src/demo/table_demo.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/egui_demo_lib/src/demo/table_demo.rs b/crates/egui_demo_lib/src/demo/table_demo.rs index 4cf0449fb6f..c7029d44e49 100644 --- a/crates/egui_demo_lib/src/demo/table_demo.rs +++ b/crates/egui_demo_lib/src/demo/table_demo.rs @@ -136,6 +136,7 @@ impl TableDemo { .size .max(ui.spacing().interact_size.y); + let available_height = ui.available_height(); let mut table = TableBuilder::new(ui) .striped(self.striped) .resizable(self.resizable) @@ -145,7 +146,8 @@ impl TableDemo { .column(Column::initial(100.0).range(40.0..=300.0)) .column(Column::initial(100.0).at_least(40.0).clip(true)) .column(Column::remainder()) - .min_scrolled_height(0.0); + .min_scrolled_height(0.0) + .max_scroll_height(available_height); if self.clickable { table = table.sense(egui::Sense::click()); From 2fabde1396e3ae5a225ec7e8619678ea1b8f9bca Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Mon, 29 Apr 2024 08:22:34 -0400 Subject: [PATCH 118/134] Add a `Display` impl for `Vec2`, `Pos2`, and `Rect` (#4428) These three types currently have a `Debug` implementation that only ever prints one decimal point. Sometimes it is useful to see more of the number, or otherwise have specific formatting. Add `Display` implementations that pass the format specification to the member `f32`s for an easier way to control what is shown when debugging. This allows doing e.g. `ui.label(format!("{:.4}", rect * scale))` which currently prints zeroes if scale is small. --- crates/emath/src/pos2.rs | 16 ++++++++++++++-- crates/emath/src/rect.rs | 16 ++++++++++++++-- crates/emath/src/vec2.rs | 16 ++++++++++++++-- 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/crates/emath/src/pos2.rs b/crates/emath/src/pos2.rs index f65efaef43d..6e0ef212ba1 100644 --- a/crates/emath/src/pos2.rs +++ b/crates/emath/src/pos2.rs @@ -1,3 +1,4 @@ +use std::fmt; use std::ops::{Add, AddAssign, Sub, SubAssign}; use crate::*; @@ -316,8 +317,19 @@ impl Div for Pos2 { } } -impl std::fmt::Debug for Pos2 { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Debug for Pos2 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "[{:.1} {:.1}]", self.x, self.y) } } + +impl fmt::Display for Pos2 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("[")?; + self.x.fmt(f)?; + f.write_str(" ")?; + self.y.fmt(f)?; + f.write_str("]")?; + Ok(()) + } +} diff --git a/crates/emath/src/rect.rs b/crates/emath/src/rect.rs index f23d3500520..c1a7f603ecb 100644 --- a/crates/emath/src/rect.rs +++ b/crates/emath/src/rect.rs @@ -1,4 +1,5 @@ use std::f32::INFINITY; +use std::fmt; use crate::*; @@ -631,12 +632,23 @@ impl Rect { } } -impl std::fmt::Debug for Rect { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Debug for Rect { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "[{:?} - {:?}]", self.min, self.max) } } +impl fmt::Display for Rect { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("[")?; + self.min.fmt(f)?; + f.write_str(" - ")?; + self.max.fmt(f)?; + f.write_str("]")?; + Ok(()) + } +} + /// from (min, max) or (left top, right bottom) impl From<[Pos2; 2]> for Rect { #[inline] diff --git a/crates/emath/src/vec2.rs b/crates/emath/src/vec2.rs index 0cab00fe62f..99f7d0c05c7 100644 --- a/crates/emath/src/vec2.rs +++ b/crates/emath/src/vec2.rs @@ -1,3 +1,4 @@ +use std::fmt; use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; use crate::Vec2b; @@ -464,12 +465,23 @@ impl Div for Vec2 { } } -impl std::fmt::Debug for Vec2 { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Debug for Vec2 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "[{:.1} {:.1}]", self.x, self.y) } } +impl fmt::Display for Vec2 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("[")?; + self.x.fmt(f)?; + f.write_str(" ")?; + self.y.fmt(f)?; + f.write_str("]")?; + Ok(()) + } +} + #[test] fn test_vec2() { macro_rules! almost_eq { From c9b24d5a5ca316fd0a8b330ee071dad2e6eab2dc Mon Sep 17 00:00:00 2001 From: Simon Niedermayr <14186588+KeKsBoTer@users.noreply.github.com> Date: Tue, 30 Apr 2024 11:47:12 +0200 Subject: [PATCH 119/134] Update to wgpu 0.20 (#4433) updates the wgpu version to 0.20 and changes the API calls accordingly. I had to update wasm-bindgen to "0.2.92". Otherwise, I got this error for the demo app: ``` error: failed to select a version for `wasm-bindgen`. ... required by package `js-sys v0.3.69` ... which satisfies dependency `js-sys = "^0.3.69"` of package `eframe v0.27.2 (/home/user/Projects/egui/crates/eframe)` ... which satisfies path dependency `eframe` (locked to 0.27.2) of package `confirm_exit v0.1.0 (/home/user/Projects/egui/examples/confirm_exit)` versions that meet the requirements `^0.2.92` are: 0.2.92 all possible versions conflict with previously selected packages. previously selected package `wasm-bindgen v0.2.90` ... which satisfies dependency `wasm-bindgen = "=0.2.90"` of package `egui_demo_app v0.27.2 (/home/user/Projects/egui/crates/egui_demo_app)` failed to select a version for `wasm-bindgen` which could resolve this conflict ``` Why is it locked to this version right now? I ran the tests, checked the web demo and my own projects, and everything seems to work fine with wgpu 0.20. --------- Co-authored-by: Andreas Reich --- .github/workflows/rust.yml | 2 +- Cargo.lock | 156 +++++++++--------- Cargo.toml | 2 +- crates/egui-wgpu/src/renderer.rs | 2 + crates/egui_demo_app/Cargo.toml | 2 +- .../egui_demo_app/src/apps/custom3d_wgpu.rs | 2 + scripts/setup_web.sh | 2 +- 7 files changed, 83 insertions(+), 85 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 26404d294a3..8aaa18faa36 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -108,7 +108,7 @@ jobs: - name: wasm-bindgen uses: jetli/wasm-bindgen-action@v0.1.0 with: - version: "0.2.88" + version: "0.2.92" - run: ./scripts/wasm_bindgen_check.sh --skip-setup diff --git a/Cargo.lock b/Cargo.lock index 1afbd85fbd0..4466ae0f344 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -144,7 +144,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "052ad56e336bcc615a214bffbeca6c181ee9550acec193f0327e0b103b033a4d" dependencies = [ "android-properties", - "bitflags 2.4.0", + "bitflags 2.5.0", "cc", "cesu8", "jni", @@ -511,9 +511,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" dependencies = [ "serde", ] @@ -650,7 +650,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b50b5a44d59a98c55a9eeb518f39bf7499ba19fd98ee7d22618687f3f10adbf" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.5.0", "log", "polling 3.3.0", "rustix 0.38.21", @@ -886,9 +886,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", @@ -896,9 +896,9 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "core-graphics" @@ -915,9 +915,9 @@ dependencies = [ [[package]] name = "core-graphics-types" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bb142d41022986c1d8ff29103a1411c8a3dfad3552f87a4f8dc50d61d4f4e33" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" dependencies = [ "bitflags 1.3.2", "core-foundation", @@ -1793,7 +1793,7 @@ version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "005459a22af86adc706522d78d360101118e2638ec21df3852fcc626e0dbb212" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.5.0", "cfg_aliases", "cgl", "core-foundation", @@ -1869,7 +1869,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.5.0", "gpu-alloc-types", ] @@ -1879,7 +1879,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.5.0", ] [[package]] @@ -1897,22 +1897,22 @@ dependencies = [ [[package]] name = "gpu-descriptor" -version = "0.2.4" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc11df1ace8e7e564511f53af41f3e42ddc95b56fd07b3f4445d2a6048bc682c" +checksum = "9c08c1f623a8d0b722b8b99f821eb0ba672a1618f0d3b16ddbee1cedd2dd8557" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.5.0", "gpu-descriptor-types", "hashbrown", ] [[package]] name = "gpu-descriptor-types" -version = "0.1.2" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bf0b36e6f090b7e1d8a4b49c0cb81c1f8376f72198c65dd3ad9ff3556b8b78c" +checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.5.0", ] [[package]] @@ -1955,7 +1955,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af2a7e73e1f34c48da31fb668a907f250794837e08faa144fd24f0b8b741e890" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.5.0", "com", "libc", "libloading 0.8.0", @@ -2198,9 +2198,9 @@ checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e" [[package]] name = "js-sys" -version = "0.3.67" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ "wasm-bindgen", ] @@ -2361,11 +2361,11 @@ dependencies = [ [[package]] name = "metal" -version = "0.27.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43f73953f8cbe511f021b58f18c3ce1c3d1ae13fe953293e13345bf83217f25" +checksum = "5637e166ea14be6063a3f8ba5ccb9a4159df7d8f6d61c02fc3d480b1f90dcfcb" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.5.0", "block", "core-graphics-types", "foreign-types", @@ -2422,12 +2422,13 @@ dependencies = [ [[package]] name = "naga" -version = "0.19.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8878eb410fc90853da3908aebfe61d73d26d4437ef850b70050461f939509899" +checksum = "e536ae46fcab0876853bd4a632ede5df4b1c2527a58f6c5a4150fe86be858231" dependencies = [ + "arrayvec", "bit-set", - "bitflags 2.4.0", + "bitflags 2.5.0", "codespan-reporting", "hexf-parse", "indexmap", @@ -2446,7 +2447,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.5.0", "jni-sys", "log", "ndk-sys", @@ -2557,7 +2558,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" dependencies = [ "malloc_buf", - "objc_exception", ] [[package]] @@ -2668,15 +2668,6 @@ dependencies = [ "objc2 0.5.1", ] -[[package]] -name = "objc_exception" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" -dependencies = [ - "cc", -] - [[package]] name = "objc_id" version = "0.1.1" @@ -3133,9 +3124,9 @@ checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[package]] name = "renderdoc-sys" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216080ab382b992234dda86873c18d4c48358f5cfcb70fd693d7f6f2131b628b" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" [[package]] name = "resvg" @@ -3205,7 +3196,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" dependencies = [ "base64", - "bitflags 2.4.0", + "bitflags 2.5.0", "serde", "serde_derive", ] @@ -3248,7 +3239,7 @@ version = "0.38.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.5.0", "errno", "libc", "linux-raw-sys 0.4.11", @@ -3477,7 +3468,7 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60e3d9941fa3bacf7c2bf4b065304faa14164151254cd16ce1b1bc8fc381600f" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.5.0", "calloop", "calloop-wayland-source", "cursor-icon", @@ -3538,7 +3529,7 @@ version = "0.3.0+sdk-1.3.268.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.5.0", ] [[package]] @@ -3668,18 +3659,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.56" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.56" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" dependencies = [ "proc-macro2", "quote", @@ -4038,9 +4029,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.90" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -4048,9 +4039,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.90" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", @@ -4063,9 +4054,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.40" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bde2032aeb86bdfaecc8b261eef3cba735cc426c1f3a3416d1e0791be95fc461" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" dependencies = [ "cfg-if", "js-sys", @@ -4075,9 +4066,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.90" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4085,9 +4076,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.90" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", @@ -4098,9 +4089,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.90" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "wayland-backend" @@ -4122,7 +4113,7 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ca7d52347346f5473bf2f56705f360e8440873052e575e55890c4fa57843ed3" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.5.0", "nix", "wayland-backend", "wayland-scanner", @@ -4134,7 +4125,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.5.0", "cursor-icon", "wayland-backend", ] @@ -4156,7 +4147,7 @@ version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e253d7107ba913923dc253967f35e8561a3c65f914543e46843c88ddd729e21c" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.5.0", "wayland-backend", "wayland-client", "wayland-scanner", @@ -4168,7 +4159,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23803551115ff9ea9bce586860c5c5a971e360825a0309264102a9495a5ff479" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.5.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -4181,7 +4172,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.5.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -4213,9 +4204,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.67" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" dependencies = [ "js-sys", "wasm-bindgen", @@ -4257,13 +4248,14 @@ checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" [[package]] name = "wgpu" -version = "0.19.1" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bfe9a310dcf2e6b85f00c46059aaeaf4184caa8e29a1ecd4b7a704c3482332d" +checksum = "32ff1bfee408e1028e2e3acbf6d32d98b08a5a059ccbf5f33305534453ba5d3e" dependencies = [ "arrayvec", "cfg-if", "cfg_aliases", + "document-features", "js-sys", "log", "naga", @@ -4282,15 +4274,16 @@ dependencies = [ [[package]] name = "wgpu-core" -version = "0.19.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b15e451d4060ada0d99a64df44e4d590213496da7c4f245572d51071e8e30ed" +checksum = "ac6a86eaa5e763e59c73cf9e97d55fffd4dfda69fd8bda19589fcf851ddfef1f" dependencies = [ "arrayvec", "bit-vec", - "bitflags 2.4.0", + "bitflags 2.5.0", "cfg_aliases", "codespan-reporting", + "document-features", "indexmap", "log", "naga", @@ -4308,14 +4301,14 @@ dependencies = [ [[package]] name = "wgpu-hal" -version = "0.19.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11f259ceb56727fb097da108d92f8a5cbdb5b74a77f9e396bd43626f67299d61" +checksum = "4d71c8ae05170583049b65ee562fd839fdc0b3e9ddb84f4e40c9d5f8ea0d4c8c" dependencies = [ "android_system_properties", "arrayvec", "ash", - "bitflags 2.4.0", + "bitflags 2.5.0", "block", "cfg_aliases", "core-graphics-types", @@ -4332,6 +4325,7 @@ dependencies = [ "log", "metal", "naga", + "ndk-sys", "objc", "once_cell", "parking_lot", @@ -4349,11 +4343,11 @@ dependencies = [ [[package]] name = "wgpu-types" -version = "0.19.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "895fcbeb772bfb049eb80b2d6e47f6c9af235284e9703c96fc0218a42ffd5af2" +checksum = "1353d9a46bff7f955a680577f34c69122628cc2076e1d6f3a9be6ef00ae793ef" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.5.0", "js-sys", "web-sys", ] @@ -4661,7 +4655,7 @@ dependencies = [ "ahash", "android-activity", "atomic-waker", - "bitflags 2.4.0", + "bitflags 2.5.0", "bytemuck", "calloop", "cfg_aliases", @@ -4767,7 +4761,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6924668544c48c0133152e7eec86d644a056ca3d09275eb8d5cdb9855f9d8699" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.5.0", "dlib", "log", "once_cell", diff --git a/Cargo.toml b/Cargo.toml index 0fd46f9ec0c..0853df671d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,7 +79,7 @@ puffin_http = "0.16" raw-window-handle = "0.6.0" thiserror = "1.0.37" web-time = "0.2" # Timekeeping for native and web -wgpu = { version = "0.19.1", default-features = false, features = [ +wgpu = { version = "0.20.0", default-features = false, features = [ # Make the renderer `Sync` even on wasm32, because it makes the code simpler: "fragile-send-sync-non-atomic-wasm", ] } diff --git a/crates/egui-wgpu/src/renderer.rs b/crates/egui-wgpu/src/renderer.rs index b028af605bd..52ce7907a50 100644 --- a/crates/egui-wgpu/src/renderer.rs +++ b/crates/egui-wgpu/src/renderer.rs @@ -293,6 +293,7 @@ impl Renderer { // 2: uint color attributes: &wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2, 2 => Uint32], }], + compilation_options: wgpu::PipelineCompilationOptions::default() }, primitive: wgpu::PrimitiveState { topology: wgpu::PrimitiveTopology::TriangleList, @@ -334,6 +335,7 @@ impl Renderer { }), write_mask: wgpu::ColorWrites::ALL, })], + compilation_options: wgpu::PipelineCompilationOptions::default() }), multiview: None, } diff --git a/crates/egui_demo_app/Cargo.toml b/crates/egui_demo_app/Cargo.toml index 4ae9572bd0a..e0fb94f64d7 100644 --- a/crates/egui_demo_app/Cargo.toml +++ b/crates/egui_demo_app/Cargo.toml @@ -85,6 +85,6 @@ rfd = { version = "0.13", optional = true } # web: [target.'cfg(target_arch = "wasm32")'.dependencies] -wasm-bindgen = "=0.2.90" +wasm-bindgen = "=0.2.92" wasm-bindgen-futures = "0.4" web-sys = "0.3" diff --git a/crates/egui_demo_app/src/apps/custom3d_wgpu.rs b/crates/egui_demo_app/src/apps/custom3d_wgpu.rs index 1676a0ba70c..95b3ee1a0af 100644 --- a/crates/egui_demo_app/src/apps/custom3d_wgpu.rs +++ b/crates/egui_demo_app/src/apps/custom3d_wgpu.rs @@ -49,11 +49,13 @@ impl Custom3d { module: &shader, entry_point: "vs_main", buffers: &[], + compilation_options: wgpu::PipelineCompilationOptions::default(), }, fragment: Some(wgpu::FragmentState { module: &shader, entry_point: "fs_main", targets: &[Some(wgpu_render_state.target_format.into())], + compilation_options: wgpu::PipelineCompilationOptions::default(), }), primitive: wgpu::PrimitiveState::default(), depth_stencil: None, diff --git a/scripts/setup_web.sh b/scripts/setup_web.sh index 36d39128b6e..51e53d4be4a 100755 --- a/scripts/setup_web.sh +++ b/scripts/setup_web.sh @@ -7,4 +7,4 @@ cd "$script_path/.." rustup target add wasm32-unknown-unknown # For generating JS bindings: -cargo install --quiet wasm-bindgen-cli --version 0.2.90 +cargo install --quiet wasm-bindgen-cli --version 0.2.92 From ded8dbd45bc8616475c364878f6b8e2f7293d12c Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 2 May 2024 17:04:25 +0200 Subject: [PATCH 120/134] Fix some clippy warning from Rust 1.78.0 (#4444) --- crates/eframe/src/epi.rs | 6 +++--- crates/eframe/src/native/glow_integration.rs | 15 --------------- crates/eframe/src/native/wgpu_integration.rs | 14 -------------- crates/eframe/src/native/winit_integration.rs | 6 ------ crates/egui-wgpu/src/renderer.rs | 2 +- crates/egui-wgpu/src/winit.rs | 4 ++-- crates/egui/src/frame_state.rs | 4 ++-- crates/egui/src/input_state.rs | 2 +- crates/egui/src/input_state/touch_state.rs | 2 +- crates/egui/src/layout.rs | 4 ++-- crates/egui/src/os.rs | 10 +++++----- crates/egui/src/style.rs | 2 +- crates/egui/src/util/undoer.rs | 4 ++-- crates/egui/src/viewport.rs | 2 +- crates/egui/src/widgets/text_edit/text_buffer.rs | 2 +- crates/egui_demo_app/src/wrap_app.rs | 2 +- .../src/easy_mark/easy_mark_highlighter.rs | 2 +- crates/egui_plot/src/items/values.rs | 14 +++++++------- crates/emath/src/rot2.rs | 4 ++-- crates/epaint/src/text/text_layout_types.rs | 6 +++--- examples/file_dialog/src/main.rs | 2 +- 21 files changed, 37 insertions(+), 72 deletions(-) diff --git a/crates/eframe/src/epi.rs b/crates/eframe/src/epi.rs index e7b53691cda..db39a08b15b 100644 --- a/crates/eframe/src/epi.rs +++ b/crates/eframe/src/epi.rs @@ -232,7 +232,7 @@ pub enum HardwareAcceleration { /// Do NOT use graphics acceleration. /// - /// On some platforms (MacOS) this is ignored and treated the same as [`Self::Preferred`]. + /// On some platforms (macOS) this is ignored and treated the same as [`Self::Preferred`]. Off, } @@ -518,10 +518,10 @@ pub enum WebGlContextOption { /// Force use WebGL2. WebGl2, - /// Use WebGl2 first. + /// Use WebGL2 first. BestFirst, - /// Use WebGl1 first + /// Use WebGL1 first CompatibilityFirst, } diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index f08ed14d623..0fbb48a09c7 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -360,21 +360,6 @@ impl WinitApp for GlowWinitApp { .map_or(0, |r| r.integration.egui_ctx.frame_nr_for(viewport_id)) } - fn is_focused(&self, window_id: WindowId) -> bool { - if let Some(running) = &self.running { - let glutin = running.glutin.borrow(); - if let Some(window_id) = glutin.viewport_from_window.get(&window_id) { - return glutin.focused_viewport == Some(*window_id); - } - } - - false - } - - fn integration(&self) -> Option<&EpiIntegration> { - self.running.as_ref().map(|r| &r.integration) - } - fn window(&self, window_id: WindowId) -> Option> { let running = self.running.as_ref()?; let glutin = running.glutin.borrow(); diff --git a/crates/eframe/src/native/wgpu_integration.rs b/crates/eframe/src/native/wgpu_integration.rs index 1287ce8a01f..f365d74ad93 100644 --- a/crates/eframe/src/native/wgpu_integration.rs +++ b/crates/eframe/src/native/wgpu_integration.rs @@ -341,20 +341,6 @@ impl WinitApp for WgpuWinitApp { .map_or(0, |r| r.integration.egui_ctx.frame_nr_for(viewport_id)) } - fn is_focused(&self, window_id: WindowId) -> bool { - if let Some(running) = &self.running { - let shared = running.shared.borrow(); - let viewport_id = shared.viewport_from_window.get(&window_id).copied(); - shared.focused_viewport.is_some() && shared.focused_viewport == viewport_id - } else { - false - } - } - - fn integration(&self) -> Option<&EpiIntegration> { - self.running.as_ref().map(|r| &r.integration) - } - fn window(&self, window_id: WindowId) -> Option> { self.running .as_ref() diff --git a/crates/eframe/src/native/winit_integration.rs b/crates/eframe/src/native/winit_integration.rs index 541253c0818..fbbd7910732 100644 --- a/crates/eframe/src/native/winit_integration.rs +++ b/crates/eframe/src/native/winit_integration.rs @@ -9,8 +9,6 @@ use egui::ViewportId; #[cfg(feature = "accesskit")] use egui_winit::accesskit_winit; -use super::epi_integration::EpiIntegration; - /// Create an egui context, restoring it from storage if possible. pub fn create_egui_context(storage: Option<&dyn crate::Storage>) -> egui::Context { crate::profile_function!(); @@ -64,10 +62,6 @@ pub trait WinitApp { /// The current frame number, as reported by egui. fn frame_nr(&self, viewport_id: ViewportId) -> u64; - fn is_focused(&self, window_id: WindowId) -> bool; - - fn integration(&self) -> Option<&EpiIntegration>; - fn window(&self, window_id: WindowId) -> Option>; fn window_id_from_viewport_id(&self, id: ViewportId) -> Option; diff --git a/crates/egui-wgpu/src/renderer.rs b/crates/egui-wgpu/src/renderer.rs index 52ce7907a50..49397c9af89 100644 --- a/crates/egui-wgpu/src/renderer.rs +++ b/crates/egui-wgpu/src/renderer.rs @@ -162,7 +162,7 @@ pub struct Renderer { texture_bind_group_layout: wgpu::BindGroupLayout, /// Map of egui texture IDs to textures and their associated bindgroups (texture view + - /// sampler). The texture may be None if the TextureId is just a handle to a user-provided + /// sampler). The texture may be None if the `TextureId` is just a handle to a user-provided /// sampler. textures: HashMap, wgpu::BindGroup)>, next_user_texture_id: u64, diff --git a/crates/egui-wgpu/src/winit.rs b/crates/egui-wgpu/src/winit.rs index 84dd0b41235..4a909bfc75f 100644 --- a/crates/egui-wgpu/src/winit.rs +++ b/crates/egui-wgpu/src/winit.rs @@ -209,7 +209,7 @@ impl Painter { if let Some(window) = window { let size = window.inner_size(); - if self.surfaces.get(&viewport_id).is_none() { + if !self.surfaces.contains_key(&viewport_id) { let surface = self.instance.create_surface(window)?; self.add_surface(surface, viewport_id, size).await?; } @@ -235,7 +235,7 @@ impl Painter { if let Some(window) = window { let size = window.inner_size(); - if self.surfaces.get(&viewport_id).is_none() { + if !self.surfaces.contains_key(&viewport_id) { let surface = unsafe { self.instance .create_surface_unsafe(wgpu::SurfaceTargetUnsafe::from_window(&window)?)? diff --git a/crates/egui/src/frame_state.rs b/crates/egui/src/frame_state.rs index 87074c2a725..f160d742345 100644 --- a/crates/egui/src/frame_state.rs +++ b/crates/egui/src/frame_state.rs @@ -21,12 +21,12 @@ pub(crate) struct FrameState { /// All [`Id`]s that were used this frame. pub(crate) used_ids: IdMap, - /// Starts off as the screen_rect, shrinks as panels are added. + /// Starts off as the `screen_rect`, shrinks as panels are added. /// The [`CentralPanel`] does not change this. /// This is the area available to Window's. pub(crate) available_rect: Rect, - /// Starts off as the screen_rect, shrinks as panels are added. + /// Starts off as the `screen_rect`, shrinks as panels are added. /// The [`CentralPanel`] retracts from this. pub(crate) unused_rect: Rect, diff --git a/crates/egui/src/input_state.rs b/crates/egui/src/input_state.rs index 9c6103e0a54..57a9e6477bf 100644 --- a/crates/egui/src/input_state.rs +++ b/crates/egui/src/input_state.rs @@ -35,7 +35,7 @@ pub struct InputState { /// State of the mouse or simple touch gestures which can be mapped to mouse operations. pub pointer: PointerState, - /// State of touches, except those covered by PointerState (like clicks and drags). + /// State of touches, except those covered by `PointerState` (like clicks and drags). /// (We keep a separate [`TouchState`] for each encountered touch device.) touch_states: BTreeMap, diff --git a/crates/egui/src/input_state/touch_state.rs b/crates/egui/src/input_state/touch_state.rs index 43053f821e5..e9425dba24c 100644 --- a/crates/egui/src/input_state/touch_state.rs +++ b/crates/egui/src/input_state/touch_state.rs @@ -72,7 +72,7 @@ pub(crate) struct TouchState { /// Active touches, if any. /// - /// TouchId is the unique identifier of the touch. It is valid as long as the finger/pen touches the surface. The + /// `TouchId` is the unique identifier of the touch. It is valid as long as the finger/pen touches the surface. The /// next touch will receive a new unique ID. /// /// Refer to [`ActiveTouch`]. diff --git a/crates/egui/src/layout.rs b/crates/egui/src/layout.rs index a044dce9814..6c93297243a 100644 --- a/crates/egui/src/layout.rs +++ b/crates/egui/src/layout.rs @@ -13,7 +13,7 @@ pub(crate) struct Region { /// Always finite. /// /// The bounding box of all child widgets, but not necessarily a tight bounding box - /// since [`Ui`](crate::Ui) can start with a non-zero min_rect size. + /// since [`Ui`](crate::Ui) can start with a non-zero `min_rect` size. pub min_rect: Rect, /// The maximum size of this [`Ui`](crate::Ui). This is a *soft max* @@ -38,7 +38,7 @@ pub(crate) struct Region { /// So one can think of `cursor` as a constraint on the available region. /// /// If something has already been added, this will point to `style.spacing.item_spacing` beyond the latest child. - /// The cursor can thus be `style.spacing.item_spacing` pixels outside of the min_rect. + /// The cursor can thus be `style.spacing.item_spacing` pixels outside of the `min_rect`. pub(crate) cursor: Rect, } diff --git a/crates/egui/src/os.rs b/crates/egui/src/os.rs index 93db910deb4..766ecf55ae6 100644 --- a/crates/egui/src/os.rs +++ b/crates/egui/src/os.rs @@ -5,19 +5,19 @@ pub enum OperatingSystem { /// Unknown OS - could be wasm Unknown, - /// Android OS. + /// Android OS Android, - /// Apple iPhone OS. + /// Apple iPhone OS IOS, - /// Linux or Unix other than Android. + /// Linux or Unix other than Android Nix, - /// MacOS. + /// macOS Mac, - /// Windows. + /// Windows Windows, } diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index aad8e3eea2d..d2d7ce99188 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -152,7 +152,7 @@ pub struct Style { /// /// The most convenient way to look something up in this is to use [`TextStyle::resolve`]. /// - /// If you would like to overwrite app text_styles + /// If you would like to overwrite app `text_styles` /// /// ``` /// # let mut ctx = egui::Context::default(); diff --git a/crates/egui/src/util/undoer.rs b/crates/egui/src/util/undoer.rs index d5f004f86ae..cd11b6d1630 100644 --- a/crates/egui/src/util/undoer.rs +++ b/crates/egui/src/util/undoer.rs @@ -59,8 +59,8 @@ pub struct Undoer { /// Stores redos immediately after a sequence of undos. /// Gets cleared every time the state changes. - /// Does not need to be a deque, because there can only be up to undos.len() redos, - /// which is already limited to settings.max_undos. + /// Does not need to be a deque, because there can only be up to `undos.len()` redos, + /// which is already limited to `settings.max_undos`. redos: Vec, #[cfg_attr(feature = "serde", serde(skip))] diff --git a/crates/egui/src/viewport.rs b/crates/egui/src/viewport.rs index cce90487721..9dd71b4747b 100644 --- a/crates/egui/src/viewport.rs +++ b/crates/egui/src/viewport.rs @@ -1071,7 +1071,7 @@ pub enum ViewportCommand { /// This is equivalent to the system keyboard shortcut for copy (e.g. CTRL + C). RequestCopy, - /// Request a paste from the clipboard to the current focused TextEdit if any. + /// Request a paste from the clipboard to the current focused `TextEdit` if any. /// /// This is equivalent to the system keyboard shortcut for paste (e.g. CTRL + V). RequestPaste, diff --git a/crates/egui/src/widgets/text_edit/text_buffer.rs b/crates/egui/src/widgets/text_edit/text_buffer.rs index e70ce695af9..ea3992c57e4 100644 --- a/crates/egui/src/widgets/text_edit/text_buffer.rs +++ b/crates/egui/src/widgets/text_edit/text_buffer.rs @@ -220,7 +220,7 @@ impl TextBuffer for String { } fn replace_with(&mut self, text: &str) { - *self = text.to_owned(); + text.clone_into(self); } fn take(&mut self) -> String { diff --git a/crates/egui_demo_app/src/wrap_app.rs b/crates/egui_demo_app/src/wrap_app.rs index 98e74d0deea..42adf2b857a 100644 --- a/crates/egui_demo_app/src/wrap_app.rs +++ b/crates/egui_demo_app/src/wrap_app.rs @@ -464,7 +464,7 @@ impl WrapApp { // Collect dropped files: ctx.input(|i| { if !i.raw.dropped_files.is_empty() { - self.dropped_files = i.raw.dropped_files.clone(); + self.dropped_files.clone_from(&i.raw.dropped_files); } }); diff --git a/crates/egui_demo_lib/src/easy_mark/easy_mark_highlighter.rs b/crates/egui_demo_lib/src/easy_mark/easy_mark_highlighter.rs index 7431accc504..5c6bc2ef086 100644 --- a/crates/egui_demo_lib/src/easy_mark/easy_mark_highlighter.rs +++ b/crates/egui_demo_lib/src/easy_mark/easy_mark_highlighter.rs @@ -14,7 +14,7 @@ impl MemoizedEasymarkHighlighter { pub fn highlight(&mut self, egui_style: &egui::Style, code: &str) -> egui::text::LayoutJob { if (&self.style, self.code.as_str()) != (egui_style, code) { self.style = egui_style.clone(); - self.code = code.to_owned(); + code.clone_into(&mut self.code); self.output = highlight_easymark(egui_style, code); } self.output.clone() diff --git a/crates/egui_plot/src/items/values.rs b/crates/egui_plot/src/items/values.rs index 5f9fe8183f3..6e9bff096b6 100644 --- a/crates/egui_plot/src/items/values.rs +++ b/crates/egui_plot/src/items/values.rs @@ -123,12 +123,12 @@ impl LineStyle { } } -impl ToString for LineStyle { - fn to_string(&self) -> String { +impl std::fmt::Display for LineStyle { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::Solid => "Solid".into(), - Self::Dotted { spacing } => format!("Dotted{spacing}Px"), - Self::Dashed { length } => format!("Dashed{length}Px"), + Self::Solid => write!(f, "Solid"), + Self::Dotted { spacing } => write!(f, "Dotted({spacing} px)"), + Self::Dashed { length } => write!(f, "Dashed({length} px)"), } } } @@ -426,9 +426,9 @@ impl ExplicitGenerator { /// Result of [`super::PlotItem::find_closest()`] search, identifies an element inside the item for immediate use pub struct ClosestElem { - /// Position of hovered-over value (or bar/box-plot/...) in PlotItem + /// Position of hovered-over value (or bar/box-plot/...) in `PlotItem` pub index: usize, - /// Squared distance from the mouse cursor (needed to compare against other PlotItems, which might be nearer) + /// Squared distance from the mouse cursor (needed to compare against other `PlotItems`, which might be nearer) pub dist_sq: f32, } diff --git a/crates/emath/src/rot2.rs b/crates/emath/src/rot2.rs index da67acb0c9c..cb2272e2f8c 100644 --- a/crates/emath/src/rot2.rs +++ b/crates/emath/src/rot2.rs @@ -18,10 +18,10 @@ use super::Vec2; #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))] pub struct Rot2 { - /// angle.sin() + /// `angle.sin()` s: f32, - /// angle.cos() + /// `angle.cos()` c: f32, } diff --git a/crates/epaint/src/text/text_layout_types.rs b/crates/epaint/src/text/text_layout_types.rs index 5e4a56d9502..f0a18ba865d 100644 --- a/crates/epaint/src/text/text_layout_types.rs +++ b/crates/epaint/src/text/text_layout_types.rs @@ -453,9 +453,9 @@ pub struct Galley { /// `rect.top()` is always 0.0. /// /// With [`LayoutJob::halign`]: - /// * [`Align::LEFT`]: rect.left() == 0.0 - /// * [`Align::Center`]: rect.center() == 0.0 - /// * [`Align::RIGHT`]: rect.right() == 0.0 + /// * [`Align::LEFT`]: `rect.left() == 0.0` + /// * [`Align::Center`]: `rect.center() == 0.0` + /// * [`Align::RIGHT`]: `rect.right() == 0.0` pub rect: Rect, /// Tight bounding box around all the meshes in all the rows. diff --git a/examples/file_dialog/src/main.rs b/examples/file_dialog/src/main.rs index d0623a73a28..267c22e111c 100644 --- a/examples/file_dialog/src/main.rs +++ b/examples/file_dialog/src/main.rs @@ -78,7 +78,7 @@ impl eframe::App for MyApp { // Collect dropped files: ctx.input(|i| { if !i.raw.dropped_files.is_empty() { - self.dropped_files = i.raw.dropped_files.clone(); + self.dropped_files.clone_from(&i.raw.dropped_files); } }); } From dce5696b670d43d65e2b846d69e0e708bc709078 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 10 May 2024 11:38:54 +0200 Subject: [PATCH 121/134] Show Cargo.lock in GitHub PR diffs --- .gitattributes | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000000..b1f5e1192e4 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +* text=auto eol=lf +Cargo.lock linguist-generated=false From 155e1389984ecc17f2eb90ab5614ccc8af487b09 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 10 May 2024 19:21:35 +0200 Subject: [PATCH 122/134] Fix debug-assert hit in panel sizing code --- crates/egui/src/containers/panel.rs | 8 ++------ crates/egui/src/placer.rs | 6 ++++++ crates/egui/src/ui.rs | 2 ++ 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/crates/egui/src/containers/panel.rs b/crates/egui/src/containers/panel.rs index ee41af87939..324e17bf91f 100644 --- a/crates/egui/src/containers/panel.rs +++ b/crates/egui/src/containers/panel.rs @@ -262,9 +262,7 @@ impl SidePanel { let frame = frame.unwrap_or_else(|| Frame::side_top_panel(ui.style())); let inner_response = frame.show(&mut panel_ui, |ui| { ui.set_min_height(ui.max_rect().height()); // Make sure the frame fills the full height - ui.set_min_width( - width_range.min - (frame.inner_margin.left + frame.inner_margin.right), - ); + ui.set_min_width((width_range.min - frame.inner_margin.sum().x).at_least(0.0)); add_contents(ui) }); @@ -730,9 +728,7 @@ impl TopBottomPanel { let frame = frame.unwrap_or_else(|| Frame::side_top_panel(ui.style())); let inner_response = frame.show(&mut panel_ui, |ui| { ui.set_min_width(ui.max_rect().width()); // Make the frame fill full width - ui.set_min_height( - height_range.min - (frame.inner_margin.top + frame.inner_margin.bottom), - ); + ui.set_min_height((height_range.min - frame.inner_margin.sum().y).at_least(0.0)); add_contents(ui) }); diff --git a/crates/egui/src/placer.rs b/crates/egui/src/placer.rs index 81c137f4e83..7a3ca433b9b 100644 --- a/crates/egui/src/placer.rs +++ b/crates/egui/src/placer.rs @@ -248,6 +248,9 @@ impl Placer { /// Set the minimum width of the ui. /// This can't shrink the ui, only make it larger. pub(crate) fn set_min_width(&mut self, width: f32) { + if width <= 0.0 { + return; + } let rect = self.next_widget_space_ignore_wrap_justify(vec2(width, 0.0)); self.region.expand_to_include_x(rect.min.x); self.region.expand_to_include_x(rect.max.x); @@ -256,6 +259,9 @@ impl Placer { /// Set the minimum height of the ui. /// This can't shrink the ui, only make it larger. pub(crate) fn set_min_height(&mut self, height: f32) { + if height <= 0.0 { + return; + } let rect = self.next_widget_space_ignore_wrap_justify(vec2(0.0, height)); self.region.expand_to_include_y(rect.min.y); self.region.expand_to_include_y(rect.max.y); diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 1215d0eeb0f..4885b06bed0 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -530,12 +530,14 @@ impl Ui { /// Set the minimum width of the ui. /// This can't shrink the ui, only make it larger. pub fn set_min_width(&mut self, width: f32) { + egui_assert!(0.0 <= width); self.placer.set_min_width(width); } /// Set the minimum height of the ui. /// This can't shrink the ui, only make it larger. pub fn set_min_height(&mut self, height: f32) { + egui_assert!(0.0 <= height); self.placer.set_min_height(height); } From f19f99180e6571a780aaa9ce6286e6fd712a4eab Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 10 May 2024 19:39:08 +0200 Subject: [PATCH 123/134] Remove `extra_asserts` and `extra_debug_asserts` feature flags (#4478) Removes `egui_assert` etc and replaces it with normal `debug_assert` calls. Previously you could opt-in to more runtime checks using feature flags. Now these extra runtime checks are always enabled for debug builds. You are most likely to encounter them if you use negative sizes or NaNs or other similar bugs. These usually indicate bugs in user space. --- crates/ecolor/Cargo.toml | 4 -- crates/ecolor/src/color32.rs | 4 +- crates/ecolor/src/lib.rs | 16 ------- crates/ecolor/src/rgba.rs | 8 ++-- crates/egui/Cargo.toml | 5 --- crates/egui/src/context.rs | 2 +- crates/egui/src/frame_state.rs | 10 ++--- crates/egui/src/grid.rs | 2 +- crates/egui/src/layout.rs | 46 ++++++++++----------- crates/egui/src/lib.rs | 16 ------- crates/egui/src/placer.rs | 12 +++--- crates/egui/src/response.rs | 2 +- crates/egui/src/ui.rs | 14 +++---- crates/egui/src/widget_rect.rs | 2 +- crates/egui/src/widgets/image.rs | 2 +- crates/egui/src/widgets/label.rs | 2 +- crates/egui/src/widgets/slider.rs | 6 +-- crates/egui_demo_app/Cargo.toml | 7 +--- crates/egui_extras/src/sizing.rs | 2 +- crates/emath/Cargo.toml | 6 --- crates/emath/src/history.rs | 2 +- crates/emath/src/lib.rs | 24 ++--------- crates/emath/src/rot2.rs | 2 +- crates/emath/src/smart_aim.rs | 6 +-- crates/epaint/Cargo.toml | 8 ---- crates/epaint/src/bezier.rs | 10 ++--- crates/epaint/src/lib.rs | 16 ------- crates/epaint/src/mesh.rs | 12 +++--- crates/epaint/src/shape.rs | 2 +- crates/epaint/src/tessellator.rs | 12 +++--- crates/epaint/src/text/text_layout_types.rs | 2 +- crates/epaint/src/textures.rs | 8 ++-- 32 files changed, 90 insertions(+), 182 deletions(-) diff --git a/crates/ecolor/Cargo.toml b/crates/ecolor/Cargo.toml index ad5d6f96369..fe4c6f444b1 100644 --- a/crates/ecolor/Cargo.toml +++ b/crates/ecolor/Cargo.toml @@ -28,10 +28,6 @@ all-features = true [features] default = [] -## Enable additional checks if debug assertions are enabled (debug builds). -extra_debug_asserts = [] -## Always enable additional checks. -extra_asserts = [] [dependencies] #! ### Optional dependencies diff --git a/crates/ecolor/src/color32.rs b/crates/ecolor/src/color32.rs index a2f06294819..807aa7a545b 100644 --- a/crates/ecolor/src/color32.rs +++ b/crates/ecolor/src/color32.rs @@ -199,7 +199,7 @@ impl Color32 { /// This is perceptually even, and faster that [`Self::linear_multiply`]. #[inline] pub fn gamma_multiply(self, factor: f32) -> Self { - crate::ecolor_assert!(0.0 <= factor && factor <= 1.0); + debug_assert!(0.0 <= factor && factor <= 1.0); let Self([r, g, b, a]) = self; Self([ (r as f32 * factor + 0.5) as u8, @@ -215,7 +215,7 @@ impl Color32 { /// You likely want to use [`Self::gamma_multiply`] instead. #[inline] pub fn linear_multiply(self, factor: f32) -> Self { - crate::ecolor_assert!(0.0 <= factor && factor <= 1.0); + debug_assert!(0.0 <= factor && factor <= 1.0); // As an unfortunate side-effect of using premultiplied alpha // we need a somewhat expensive conversion to linear space and back. Rgba::from(self).multiply(factor).into() diff --git a/crates/ecolor/src/lib.rs b/crates/ecolor/src/lib.rs index a7072d8e89a..a9400d9de61 100644 --- a/crates/ecolor/src/lib.rs +++ b/crates/ecolor/src/lib.rs @@ -135,22 +135,6 @@ pub fn gamma_from_linear(linear: f32) -> f32 { // ---------------------------------------------------------------------------- -/// An assert that is only active when `epaint` is compiled with the `extra_asserts` feature -/// or with the `extra_debug_asserts` feature in debug builds. -#[macro_export] -macro_rules! ecolor_assert { - ($($arg: tt)*) => { - if cfg!(any( - feature = "extra_asserts", - all(feature = "extra_debug_asserts", debug_assertions), - )) { - assert!($($arg)*); - } - } -} - -// ---------------------------------------------------------------------------- - /// Cheap and ugly. /// Made for graying out disabled `Ui`s. pub fn tint_color_towards(color: Color32, target: Color32) -> Color32 { diff --git a/crates/ecolor/src/rgba.rs b/crates/ecolor/src/rgba.rs index 36cb33bd599..900286cda43 100644 --- a/crates/ecolor/src/rgba.rs +++ b/crates/ecolor/src/rgba.rs @@ -98,22 +98,22 @@ impl Rgba { #[inline] pub fn from_luminance_alpha(l: f32, a: f32) -> Self { - crate::ecolor_assert!(0.0 <= l && l <= 1.0); - crate::ecolor_assert!(0.0 <= a && a <= 1.0); + debug_assert!(0.0 <= l && l <= 1.0); + debug_assert!(0.0 <= a && a <= 1.0); Self([l * a, l * a, l * a, a]) } /// Transparent black #[inline] pub fn from_black_alpha(a: f32) -> Self { - crate::ecolor_assert!(0.0 <= a && a <= 1.0); + debug_assert!(0.0 <= a && a <= 1.0); Self([0.0, 0.0, 0.0, a]) } /// Transparent white #[inline] pub fn from_white_alpha(a: f32) -> Self { - crate::ecolor_assert!(0.0 <= a && a <= 1.0, "a: {}", a); + debug_assert!(0.0 <= a && a <= 1.0, "a: {a}"); Self([a, a, a, a]) } diff --git a/crates/egui/Cargo.toml b/crates/egui/Cargo.toml index a1929f060ad..909beba133a 100644 --- a/crates/egui/Cargo.toml +++ b/crates/egui/Cargo.toml @@ -52,11 +52,6 @@ deadlock_detection = ["epaint/deadlock_detection"] ## If you plan on specifying your own fonts you may disable this feature. default_fonts = ["epaint/default_fonts"] -## Enable additional checks if debug assertions are enabled (debug builds). -extra_debug_asserts = ["epaint/extra_debug_asserts"] -## Always enable additional checks. -extra_asserts = ["epaint/extra_asserts"] - ## Turn on the `log` feature, that makes egui log some errors using the [`log`](https://docs.rs/log) crate. log = ["dep:log", "epaint/log"] diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index f697fb4b8e5..04bdd16432a 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -1748,7 +1748,7 @@ impl Context { let name = name.into(); let image = image.into(); let max_texture_side = self.input(|i| i.max_texture_side); - crate::egui_assert!( + debug_assert!( image.width() <= max_texture_side && image.height() <= max_texture_side, "Texture {:?} has size {}x{}, but the maximum texture side is {}", name, diff --git a/crates/egui/src/frame_state.rs b/crates/egui/src/frame_state.rs index f160d742345..5f966d117a0 100644 --- a/crates/egui/src/frame_state.rs +++ b/crates/egui/src/frame_state.rs @@ -117,7 +117,7 @@ impl FrameState { /// This is the "background" area, what egui doesn't cover with panels (but may cover with windows). /// This is also the area to which windows are constrained. pub(crate) fn available_rect(&self) -> Rect { - crate::egui_assert!( + debug_assert!( self.available_rect.is_finite(), "Called `available_rect()` before `Context::run()`" ); @@ -126,7 +126,7 @@ impl FrameState { /// Shrink `available_rect`. pub(crate) fn allocate_left_panel(&mut self, panel_rect: Rect) { - crate::egui_assert!( + debug_assert!( panel_rect.min.distance(self.available_rect.min) < 0.1, "Mismatching left panel. You must not create a panel from within another panel." ); @@ -137,7 +137,7 @@ impl FrameState { /// Shrink `available_rect`. pub(crate) fn allocate_right_panel(&mut self, panel_rect: Rect) { - crate::egui_assert!( + debug_assert!( panel_rect.max.distance(self.available_rect.max) < 0.1, "Mismatching right panel. You must not create a panel from within another panel." ); @@ -148,7 +148,7 @@ impl FrameState { /// Shrink `available_rect`. pub(crate) fn allocate_top_panel(&mut self, panel_rect: Rect) { - crate::egui_assert!( + debug_assert!( panel_rect.min.distance(self.available_rect.min) < 0.1, "Mismatching top panel. You must not create a panel from within another panel." ); @@ -159,7 +159,7 @@ impl FrameState { /// Shrink `available_rect`. pub(crate) fn allocate_bottom_panel(&mut self, panel_rect: Rect) { - crate::egui_assert!( + debug_assert!( panel_rect.max.distance(self.available_rect.max) < 0.1, "Mismatching bottom panel. You must not create a panel from within another panel." ); diff --git a/crates/egui/src/grid.rs b/crates/egui/src/grid.rs index 374902e3a4b..b888f756b5e 100644 --- a/crates/egui/src/grid.rs +++ b/crates/egui/src/grid.rs @@ -85,7 +85,7 @@ impl GridLayout { // TODO(emilk): respect current layout let initial_available = ui.placer().max_rect().intersect(ui.cursor()); - crate::egui_assert!( + debug_assert!( initial_available.min.x.is_finite(), "Grid not yet available for right-to-left layouts" ); diff --git a/crates/egui/src/layout.rs b/crates/egui/src/layout.rs index 6c93297243a..15ed2fef079 100644 --- a/crates/egui/src/layout.rs +++ b/crates/egui/src/layout.rs @@ -1,4 +1,4 @@ -use crate::{egui_assert, emath::*, Align}; +use crate::{emath::*, Align}; use std::f32::INFINITY; // ---------------------------------------------------------------------------- @@ -66,9 +66,9 @@ impl Region { } pub fn sanity_check(&self) { - egui_assert!(!self.min_rect.any_nan()); - egui_assert!(!self.max_rect.any_nan()); - egui_assert!(!self.cursor.any_nan()); + debug_assert!(!self.min_rect.any_nan()); + debug_assert!(!self.max_rect.any_nan()); + debug_assert!(!self.cursor.any_nan()); } } @@ -389,8 +389,8 @@ impl Layout { /// ## Doing layout impl Layout { pub fn align_size_within_rect(&self, size: Vec2, outer: Rect) -> Rect { - egui_assert!(size.x >= 0.0 && size.y >= 0.0); - egui_assert!(!outer.is_negative()); + debug_assert!(size.x >= 0.0 && size.y >= 0.0); + debug_assert!(!outer.is_negative()); self.align2().align_size_within_rect(size, outer) } @@ -416,8 +416,8 @@ impl Layout { } pub(crate) fn region_from_max_rect(&self, max_rect: Rect) -> Region { - egui_assert!(!max_rect.any_nan()); - egui_assert!(max_rect.is_finite()); + debug_assert!(!max_rect.any_nan()); + debug_assert!(max_rect.is_finite()); let mut region = Region { min_rect: Rect::NOTHING, // temporary max_rect, @@ -450,9 +450,9 @@ impl Layout { /// Given the cursor in the region, how much space is available /// for the next widget? fn available_from_cursor_max_rect(&self, cursor: Rect, max_rect: Rect) -> Rect { - egui_assert!(!cursor.any_nan()); - egui_assert!(!max_rect.any_nan()); - egui_assert!(max_rect.is_finite()); + debug_assert!(!cursor.any_nan()); + debug_assert!(!max_rect.any_nan()); + debug_assert!(max_rect.is_finite()); // NOTE: in normal top-down layout the cursor has moved below the current max_rect, // but the available shouldn't be negative. @@ -506,7 +506,7 @@ impl Layout { avail.max.y = y; } - egui_assert!(!avail.any_nan()); + debug_assert!(!avail.any_nan()); avail } @@ -517,7 +517,7 @@ impl Layout { /// Use `justify_and_align` to get the inner `widget_rect`. pub(crate) fn next_frame(&self, region: &Region, child_size: Vec2, spacing: Vec2) -> Rect { region.sanity_check(); - egui_assert!(child_size.x >= 0.0 && child_size.y >= 0.0); + debug_assert!(child_size.x >= 0.0 && child_size.y >= 0.0); if self.main_wrap { let available_size = self.available_rect_before_wrap(region).size(); @@ -597,7 +597,7 @@ impl Layout { fn next_frame_ignore_wrap(&self, region: &Region, child_size: Vec2) -> Rect { region.sanity_check(); - egui_assert!(child_size.x >= 0.0 && child_size.y >= 0.0); + debug_assert!(child_size.x >= 0.0 && child_size.y >= 0.0); let available_rect = self.available_rect_before_wrap(region); @@ -630,16 +630,16 @@ impl Layout { frame_rect = frame_rect.translate(Vec2::Y * (region.cursor.top() - frame_rect.top())); } - egui_assert!(!frame_rect.any_nan()); - egui_assert!(!frame_rect.is_negative()); + debug_assert!(!frame_rect.any_nan()); + debug_assert!(!frame_rect.is_negative()); frame_rect } /// Apply justify (fill width/height) and/or alignment after calling `next_space`. pub(crate) fn justify_and_align(&self, frame: Rect, mut child_size: Vec2) -> Rect { - egui_assert!(child_size.x >= 0.0 && child_size.y >= 0.0); - egui_assert!(!frame.is_negative()); + debug_assert!(child_size.x >= 0.0 && child_size.y >= 0.0); + debug_assert!(!frame.is_negative()); if self.horizontal_justify() { child_size.x = child_size.x.at_least(frame.width()); // fill full width @@ -657,10 +657,10 @@ impl Layout { ) -> Rect { let frame = self.next_frame_ignore_wrap(region, size); let rect = self.align_size_within_rect(size, frame); - egui_assert!(!rect.any_nan()); - egui_assert!(!rect.is_negative()); - egui_assert!((rect.width() - size.x).abs() < 1.0 || size.x == f32::INFINITY); - egui_assert!((rect.height() - size.y).abs() < 1.0 || size.y == f32::INFINITY); + debug_assert!(!rect.any_nan()); + debug_assert!(!rect.is_negative()); + debug_assert!((rect.width() - size.x).abs() < 1.0 || size.x == f32::INFINITY); + debug_assert!((rect.height() - size.y).abs() < 1.0 || size.y == f32::INFINITY); rect } @@ -703,7 +703,7 @@ impl Layout { widget_rect: Rect, item_spacing: Vec2, ) { - egui_assert!(!cursor.any_nan()); + debug_assert!(!cursor.any_nan()); if self.main_wrap { if cursor.intersects(frame_rect.shrink(1.0)) { // make row/column larger if necessary diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index 30e3b804f11..a846acc701e 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -549,22 +549,6 @@ macro_rules! github_link_file { // ---------------------------------------------------------------------------- -/// An assert that is only active when `egui` is compiled with the `extra_asserts` feature -/// or with the `extra_debug_asserts` feature in debug builds. -#[macro_export] -macro_rules! egui_assert { - ($($arg: tt)*) => { - if cfg!(any( - feature = "extra_asserts", - all(feature = "extra_debug_asserts", debug_assertions), - )) { - assert!($($arg)*); - } - } -} - -// ---------------------------------------------------------------------------- - /// The minus character: pub(crate) const MINUS_CHAR_STR: &str = "−"; diff --git a/crates/egui/src/placer.rs b/crates/egui/src/placer.rs index 7a3ca433b9b..0be6b413b22 100644 --- a/crates/egui/src/placer.rs +++ b/crates/egui/src/placer.rs @@ -106,7 +106,7 @@ impl Placer { /// This is what you then pass to `advance_after_rects`. /// Use `justify_and_align` to get the inner `widget_rect`. pub(crate) fn next_space(&self, child_size: Vec2, item_spacing: Vec2) -> Rect { - egui_assert!(child_size.is_finite() && child_size.x >= 0.0 && child_size.y >= 0.0); + debug_assert!(child_size.is_finite() && child_size.x >= 0.0 && child_size.y >= 0.0); self.region.sanity_check(); if let Some(grid) = &self.grid { grid.next_cell(self.region.cursor, child_size) @@ -127,8 +127,8 @@ impl Placer { /// Apply justify or alignment after calling `next_space`. pub(crate) fn justify_and_align(&self, rect: Rect, child_size: Vec2) -> Rect { - crate::egui_assert!(!rect.any_nan()); - crate::egui_assert!(!child_size.any_nan()); + debug_assert!(!rect.any_nan()); + debug_assert!(!child_size.any_nan()); if let Some(grid) = &self.grid { grid.justify_and_align(rect, child_size) @@ -140,7 +140,7 @@ impl Placer { /// Advance the cursor by this many points. /// [`Self::min_rect`] will expand to contain the cursor. pub(crate) fn advance_cursor(&mut self, amount: f32) { - crate::egui_assert!( + debug_assert!( self.grid.is_none(), "You cannot advance the cursor when in a grid layout" ); @@ -158,8 +158,8 @@ impl Placer { widget_rect: Rect, item_spacing: Vec2, ) { - egui_assert!(!frame_rect.any_nan()); - egui_assert!(!widget_rect.any_nan()); + debug_assert!(!frame_rect.any_nan()); + debug_assert!(!widget_rect.any_nan()); self.region.sanity_check(); if let Some(grid) = &mut self.grid { diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index 44eb4f74bd6..d980bd78eb6 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -936,7 +936,7 @@ impl Response { /// You may not call [`Self::interact`] on the resulting `Response`. pub fn union(&self, other: Self) -> Self { assert!(self.ctx == other.ctx); - crate::egui_assert!( + debug_assert!( self.layer_id == other.layer_id, "It makes no sense to combine Responses from two different layers" ); diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 4885b06bed0..00b7beef6e4 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -111,7 +111,7 @@ impl Ui { layout: Layout, id_source: impl Hash, ) -> Self { - crate::egui_assert!(!max_rect.any_nan()); + debug_assert!(!max_rect.any_nan()); let next_auto_id_source = Id::new(self.next_auto_id_source).with("child").value(); self.next_auto_id_source = self.next_auto_id_source.wrapping_add(1); let child_ui = Ui { @@ -530,14 +530,14 @@ impl Ui { /// Set the minimum width of the ui. /// This can't shrink the ui, only make it larger. pub fn set_min_width(&mut self, width: f32) { - egui_assert!(0.0 <= width); + debug_assert!(0.0 <= width); self.placer.set_min_width(width); } /// Set the minimum height of the ui. /// This can't shrink the ui, only make it larger. pub fn set_min_height(&mut self, height: f32) { - egui_assert!(0.0 <= height); + debug_assert!(0.0 <= height); self.placer.set_min_height(height); } @@ -847,7 +847,7 @@ impl Ui { fn allocate_space_impl(&mut self, desired_size: Vec2) -> Rect { let item_spacing = self.spacing().item_spacing; let frame_rect = self.placer.next_space(desired_size, item_spacing); - egui_assert!(!frame_rect.any_nan()); + debug_assert!(!frame_rect.any_nan()); let widget_rect = self.placer.justify_and_align(frame_rect, desired_size); self.placer @@ -870,7 +870,7 @@ impl Ui { /// Allocate a rect without interacting with it. pub fn advance_cursor_after_rect(&mut self, rect: Rect) -> Id { - egui_assert!(!rect.any_nan()); + debug_assert!(!rect.any_nan()); let item_spacing = self.spacing().item_spacing; self.placer.advance_after_rects(rect, rect, item_spacing); @@ -939,7 +939,7 @@ impl Ui { layout: Layout, add_contents: Box R + 'c>, ) -> InnerResponse { - crate::egui_assert!(desired_size.x >= 0.0 && desired_size.y >= 0.0); + debug_assert!(desired_size.x >= 0.0 && desired_size.y >= 0.0); let item_spacing = self.spacing().item_spacing; let frame_rect = self.placer.next_space(desired_size, item_spacing); let child_rect = self.placer.justify_and_align(frame_rect, desired_size); @@ -964,7 +964,7 @@ impl Ui { max_rect: Rect, add_contents: impl FnOnce(&mut Self) -> R, ) -> InnerResponse { - egui_assert!(max_rect.is_finite()); + debug_assert!(max_rect.is_finite()); let mut child_ui = self.child_ui(max_rect, *self.layout()); let ret = add_contents(&mut child_ui); let final_child_rect = child_ui.min_rect(); diff --git a/crates/egui/src/widget_rect.rs b/crates/egui/src/widget_rect.rs index 7c502f13927..76d03270f26 100644 --- a/crates/egui/src/widget_rect.rs +++ b/crates/egui/src/widget_rect.rs @@ -139,7 +139,7 @@ impl WidgetRects { // e.g. calling `response.interact(…)` to add more interaction. let (idx_in_layer, existing) = entry.get_mut(); - egui_assert!( + debug_assert!( existing.layer_id == widget_rect.layer_id, "Widget changed layer_id during the frame" ); diff --git a/crates/egui/src/widgets/image.rs b/crates/egui/src/widgets/image.rs index 15b9dcddb2e..a5aecdce931 100644 --- a/crates/egui/src/widgets/image.rs +++ b/crates/egui/src/widgets/image.rs @@ -746,7 +746,7 @@ pub fn paint_texture_at( Some((rot, origin)) => { // TODO(emilk): implement this using `PathShape` (add texture support to it). // This will also give us anti-aliasing of rotated images. - egui_assert!( + debug_assert!( options.rounding == Rounding::ZERO, "Image had both rounding and rotation. Please pick only one" ); diff --git a/crates/egui/src/widgets/label.rs b/crates/egui/src/widgets/label.rs index 10cefc5fb6a..f09a17baeb2 100644 --- a/crates/egui/src/widgets/label.rs +++ b/crates/egui/src/widgets/label.rs @@ -170,7 +170,7 @@ impl Label { let cursor = ui.cursor(); let first_row_indentation = available_width - ui.available_size_before_wrap().x; - egui_assert!(first_row_indentation.is_finite()); + debug_assert!(first_row_indentation.is_finite()); layout_job.wrap.max_width = available_width; layout_job.first_row_min_height = cursor.height(); diff --git a/crates/egui/src/widgets/slider.rs b/crates/egui/src/widgets/slider.rs index f303464a4fe..644694cbe81 100644 --- a/crates/egui/src/widgets/slider.rs +++ b/crates/egui/src/widgets/slider.rs @@ -983,7 +983,7 @@ fn value_from_normalized(normalized: f64, range: RangeInclusive, spec: &Sli } } } else { - crate::egui_assert!( + debug_assert!( min.is_finite() && max.is_finite(), "You should use a logarithmic range" ); @@ -1032,7 +1032,7 @@ fn normalized_from_value(value: f64, range: RangeInclusive, spec: &SliderSp } } } else { - crate::egui_assert!( + debug_assert!( min.is_finite() && max.is_finite(), "You should use a logarithmic range" ); @@ -1080,6 +1080,6 @@ fn logarithmic_zero_cutoff(min: f64, max: f64) -> f64 { }; let cutoff = min_magnitude / (min_magnitude + max_magnitude); - crate::egui_assert!(0.0 <= cutoff && cutoff <= 1.0); + debug_assert!(0.0 <= cutoff && cutoff <= 1.0); cutoff } diff --git a/crates/egui_demo_app/Cargo.toml b/crates/egui_demo_app/Cargo.toml index e0fb94f64d7..0491f5ff934 100644 --- a/crates/egui_demo_app/Cargo.toml +++ b/crates/egui_demo_app/Cargo.toml @@ -44,12 +44,7 @@ chrono = { version = "0.4", default-features = false, features = [ eframe = { workspace = true, default-features = false, features = [ "web_screen_reader", ] } -egui = { workspace = true, features = [ - "callstack", - "default", - "extra_debug_asserts", - "log", -] } +egui = { workspace = true, features = ["callstack", "default", "log"] } egui_demo_lib = { workspace = true, features = ["default", "chrono"] } egui_extras = { workspace = true, features = ["default", "image"] } log.workspace = true diff --git a/crates/egui_extras/src/sizing.rs b/crates/egui_extras/src/sizing.rs index 1ff7a1a5363..2c380ae6179 100644 --- a/crates/egui_extras/src/sizing.rs +++ b/crates/egui_extras/src/sizing.rs @@ -32,7 +32,7 @@ impl Size { /// Relative size relative to all available space. Values must be in range `0.0..=1.0`. pub fn relative(fraction: f32) -> Self { - egui::egui_assert!(0.0 <= fraction && fraction <= 1.0); + debug_assert!(0.0 <= fraction && fraction <= 1.0); Self::Relative { fraction, range: Rangef::new(0.0, f32::INFINITY), diff --git a/crates/emath/Cargo.toml b/crates/emath/Cargo.toml index ae0153cc9f2..99fc1b004b8 100644 --- a/crates/emath/Cargo.toml +++ b/crates/emath/Cargo.toml @@ -25,12 +25,6 @@ all-features = true [features] default = [] -## Enable additional checks if debug assertions are enabled (debug builds). -extra_debug_asserts = [] - -## Always enable additional checks. -extra_asserts = [] - [dependencies] #! ### Optional dependencies diff --git a/crates/emath/src/history.rs b/crates/emath/src/history.rs index d85a27a398a..6aafd0af145 100644 --- a/crates/emath/src/history.rs +++ b/crates/emath/src/history.rs @@ -126,7 +126,7 @@ where /// Values must be added with a monotonically increasing time, or at least not decreasing. pub fn add(&mut self, now: f64, value: T) { if let Some((last_time, _)) = self.values.back() { - crate::emath_assert!(now >= *last_time, "Time shouldn't move backwards"); + debug_assert!(*last_time <= now, "Time shouldn't move backwards"); } self.total_count += 1; self.values.push_back((now, value)); diff --git a/crates/emath/src/lib.rs b/crates/emath/src/lib.rs index d3e2f5e0313..4ac46f21947 100644 --- a/crates/emath/src/lib.rs +++ b/crates/emath/src/lib.rs @@ -146,7 +146,7 @@ where { let from = from.into(); let to = to.into(); - crate::emath_assert!(from.start() != from.end()); + debug_assert!(from.start() != from.end()); let t = (x - *from.start()) / (*from.end() - *from.start()); lerp(to, t) } @@ -170,7 +170,7 @@ where } else if *from.end() <= x { *to.end() } else { - crate::emath_assert!(from.start() != from.end()); + debug_assert!(from.start() != from.end()); let t = (x - *from.start()) / (*from.end() - *from.start()); // Ensure no numerical inaccuracies sneak in: if T::ONE <= t { @@ -194,8 +194,8 @@ pub fn format_with_minimum_decimals(value: f64, decimals: usize) -> String { pub fn format_with_decimals_in_range(value: f64, decimal_range: RangeInclusive) -> String { let min_decimals = *decimal_range.start(); let max_decimals = *decimal_range.end(); - crate::emath_assert!(min_decimals <= max_decimals); - crate::emath_assert!(max_decimals < 100); + debug_assert!(min_decimals <= max_decimals); + debug_assert!(max_decimals < 100); let max_decimals = max_decimals.min(16); let min_decimals = min_decimals.min(max_decimals); @@ -430,19 +430,3 @@ pub fn ease_in_ease_out(t: f32) -> f32 { let t = t.clamp(0.0, 1.0); (3.0 * t * t - 2.0 * t * t * t).clamp(0.0, 1.0) } - -// ---------------------------------------------------------------------------- - -/// An assert that is only active when `emath` is compiled with the `extra_asserts` feature -/// or with the `extra_debug_asserts` feature in debug builds. -#[macro_export] -macro_rules! emath_assert { - ($($arg: tt)*) => { - if cfg!(any( - feature = "extra_asserts", - all(feature = "extra_debug_asserts", debug_assertions), - )) { - assert!($($arg)*); - } - } -} diff --git a/crates/emath/src/rot2.rs b/crates/emath/src/rot2.rs index cb2272e2f8c..f9e0ae6462c 100644 --- a/crates/emath/src/rot2.rs +++ b/crates/emath/src/rot2.rs @@ -84,7 +84,7 @@ impl Rot2 { c: self.c / l, s: self.s / l, }; - crate::emath_assert!(ret.is_finite()); + debug_assert!(ret.is_finite()); ret } } diff --git a/crates/emath/src/smart_aim.rs b/crates/emath/src/smart_aim.rs index bd0f7b66197..23e34abf701 100644 --- a/crates/emath/src/smart_aim.rs +++ b/crates/emath/src/smart_aim.rs @@ -33,7 +33,7 @@ pub fn best_in_range_f64(min: f64, max: f64) -> f64 { if !max.is_finite() { return min; } - crate::emath_assert!(min.is_finite() && max.is_finite()); + debug_assert!(min.is_finite() && max.is_finite()); let min_exponent = min.log10(); let max_exponent = max.log10(); @@ -82,7 +82,7 @@ fn is_integer(f: f64) -> bool { } fn to_decimal_string(v: f64) -> [i32; NUM_DECIMALS] { - crate::emath_assert!(v < 10.0, "{:?}", v); + debug_assert!(v < 10.0, "{v:?}"); let mut digits = [0; NUM_DECIMALS]; let mut v = v.abs(); for r in &mut digits { @@ -104,7 +104,7 @@ fn from_decimal_string(s: &[i32]) -> f64 { /// Find the simplest integer in the range [min, max] fn simplest_digit_closed_range(min: i32, max: i32) -> i32 { - crate::emath_assert!(1 <= min && min <= max && max <= 9); + debug_assert!(1 <= min && min <= max && max <= 9); if min <= 5 && 5 <= max { 5 } else { diff --git a/crates/epaint/Cargo.toml b/crates/epaint/Cargo.toml index 861afb1a14b..852066d046c 100644 --- a/crates/epaint/Cargo.toml +++ b/crates/epaint/Cargo.toml @@ -52,14 +52,6 @@ deadlock_detection = ["dep:backtrace"] ## If you plan on specifying your own fonts you may disable this feature. default_fonts = [] -## Enable additional checks if debug assertions are enabled (debug builds). -extra_debug_asserts = [ - "emath/extra_debug_asserts", - "ecolor/extra_debug_asserts", -] -## Always enable additional checks. -extra_asserts = ["emath/extra_asserts", "ecolor/extra_asserts"] - ## Turn on the `log` feature, that makes egui log some errors using the [`log`](https://docs.rs/log) crate. log = ["dep:log"] diff --git a/crates/epaint/src/bezier.rs b/crates/epaint/src/bezier.rs index 4ad9a28228f..6f61feb0ac3 100644 --- a/crates/epaint/src/bezier.rs +++ b/crates/epaint/src/bezier.rs @@ -141,8 +141,8 @@ impl CubicBezierShape { /// split the original cubic curve into a new one within a range. pub fn split_range(&self, t_range: Range) -> Self { - crate::epaint_assert!( - t_range.start >= 0.0 && t_range.end <= 1.0 && t_range.start <= t_range.end, + debug_assert!( + 0.0 <= t_range.start && t_range.end <= 1.0 && t_range.start <= t_range.end, "range should be in [0.0,1.0]" ); @@ -178,7 +178,7 @@ impl CubicBezierShape { // https://scholarsarchive.byu.edu/cgi/viewcontent.cgi?article=1000&context=facpub#section.10.6 // and the error metric from the caffein owl blog post http://caffeineowl.com/graphics/2d/vectorial/cubic2quad01.html pub fn num_quadratics(&self, tolerance: f32) -> u32 { - crate::epaint_assert!(tolerance > 0.0, "the tolerance should be positive"); + debug_assert!(tolerance > 0.0, "the tolerance should be positive"); let x = self.points[0].x - 3.0 * self.points[1].x + 3.0 * self.points[2].x - self.points[3].x; @@ -273,7 +273,7 @@ impl CubicBezierShape { /// [Bézier Curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve#Cubic_B.C3.A9zier_curves) /// pub fn sample(&self, t: f32) -> Pos2 { - crate::epaint_assert!( + debug_assert!( t >= 0.0 && t <= 1.0, "the sample value should be in [0.0,1.0]" ); @@ -496,7 +496,7 @@ impl QuadraticBezierShape { /// [Bézier Curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve#Quadratic_B.C3.A9zier_curves) /// pub fn sample(&self, t: f32) -> Pos2 { - crate::epaint_assert!( + debug_assert!( t >= 0.0 && t <= 1.0, "the sample value should be in [0.0,1.0]" ); diff --git a/crates/epaint/src/lib.rs b/crates/epaint/src/lib.rs index c8c47bdf9b2..db48fce6876 100644 --- a/crates/epaint/src/lib.rs +++ b/crates/epaint/src/lib.rs @@ -138,22 +138,6 @@ pub enum Primitive { Callback(PaintCallback), } -// ---------------------------------------------------------------------------- - -/// An assert that is only active when `epaint` is compiled with the `extra_asserts` feature -/// or with the `extra_debug_asserts` feature in debug builds. -#[macro_export] -macro_rules! epaint_assert { - ($($arg: tt)*) => { - if cfg!(any( - feature = "extra_asserts", - all(feature = "extra_debug_asserts", debug_assertions), - )) { - assert!($($arg)*); - } - } -} - // --------------------------------------------------------------------------- /// Was epaint compiled with the `rayon` feature? diff --git a/crates/epaint/src/mesh.rs b/crates/epaint/src/mesh.rs index 7d5a51965d9..9dd919274e6 100644 --- a/crates/epaint/src/mesh.rs +++ b/crates/epaint/src/mesh.rs @@ -109,7 +109,7 @@ impl Mesh { /// Append all the indices and vertices of `other` to `self`. pub fn append(&mut self, other: Self) { crate::profile_function!(); - crate::epaint_assert!(other.is_valid()); + debug_assert!(other.is_valid()); if self.is_empty() { *self = other; @@ -121,7 +121,7 @@ impl Mesh { /// Append all the indices and vertices of `other` to `self` without /// taking ownership. pub fn append_ref(&mut self, other: &Self) { - crate::epaint_assert!(other.is_valid()); + debug_assert!(other.is_valid()); if self.is_empty() { self.texture_id = other.texture_id; @@ -140,7 +140,7 @@ impl Mesh { #[inline(always)] pub fn colored_vertex(&mut self, pos: Pos2, color: Color32) { - crate::epaint_assert!(self.texture_id == TextureId::default()); + debug_assert!(self.texture_id == TextureId::default()); self.vertices.push(Vertex { pos, uv: WHITE_UV, @@ -203,7 +203,7 @@ impl Mesh { /// Uniformly colored rectangle. #[inline(always)] pub fn add_colored_rect(&mut self, rect: Rect, color: Color32) { - crate::epaint_assert!(self.texture_id == TextureId::default()); + debug_assert!(self.texture_id == TextureId::default()); self.add_rect_with_uv(rect, [WHITE_UV, WHITE_UV].into(), color); } @@ -212,7 +212,7 @@ impl Mesh { /// Splits this mesh into many smaller meshes (if needed) /// where the smaller meshes have 16-bit indices. pub fn split_to_u16(self) -> Vec { - crate::epaint_assert!(self.is_valid()); + debug_assert!(self.is_valid()); const MAX_SIZE: u32 = std::u16::MAX as u32; @@ -265,7 +265,7 @@ impl Mesh { vertices: self.vertices[(min_vindex as usize)..=(max_vindex as usize)].to_vec(), texture_id: self.texture_id, }; - crate::epaint_assert!(mesh.is_valid()); + debug_assert!(mesh.is_valid()); output.push(mesh); } output diff --git a/crates/epaint/src/shape.rs b/crates/epaint/src/shape.rs index 593cc78b0f3..70f9a8b5cfd 100644 --- a/crates/epaint/src/shape.rs +++ b/crates/epaint/src/shape.rs @@ -313,7 +313,7 @@ impl Shape { #[inline] pub fn mesh(mesh: Mesh) -> Self { - crate::epaint_assert!(mesh.is_valid()); + debug_assert!(mesh.is_valid()); Self::Mesh(mesh) } diff --git a/crates/epaint/src/tessellator.rs b/crates/epaint/src/tessellator.rs index 4135cc58d22..71a4b780515 100644 --- a/crates/epaint/src/tessellator.rs +++ b/crates/epaint/src/tessellator.rs @@ -1311,7 +1311,7 @@ impl Tessellator { crate::profile_scope!("mesh"); if self.options.validate_meshes && !mesh.is_valid() { - crate::epaint_assert!(false, "Invalid Mesh in Shape::Mesh"); + debug_assert!(false, "Invalid Mesh in Shape::Mesh"); return; } // note: `append` still checks if the mesh is valid if extra asserts are enabled. @@ -1480,7 +1480,7 @@ impl Tessellator { /// * `out`: triangles are appended to this. pub fn tessellate_mesh(&mut self, mesh: &Mesh, out: &mut Mesh) { if !mesh.is_valid() { - crate::epaint_assert!(false, "Invalid Mesh in Shape::Mesh"); + debug_assert!(false, "Invalid Mesh in Shape::Mesh"); return; } @@ -1554,7 +1554,7 @@ impl Tessellator { } if *fill != Color32::TRANSPARENT { - crate::epaint_assert!( + debug_assert!( closed, "You asked to fill a path that is not closed. That makes no sense." ); @@ -1760,7 +1760,7 @@ impl Tessellator { color = color.gamma_multiply(*opacity_factor); } - crate::epaint_assert!(color != Color32::PLACEHOLDER, "A placeholder color made it to the tessellator. You forgot to set a fallback color."); + debug_assert!(color != Color32::PLACEHOLDER, "A placeholder color made it to the tessellator. You forgot to set a fallback color."); let offset = if *angle == 0.0 { pos.to_vec2() @@ -1864,7 +1864,7 @@ impl Tessellator { self.scratchpad_path.add_open_points(points); } if fill != Color32::TRANSPARENT { - crate::epaint_assert!( + debug_assert!( closed, "You asked to fill a path that is not closed. That makes no sense." ); @@ -1946,7 +1946,7 @@ impl Tessellator { for clipped_primitive in &clipped_primitives { if let Primitive::Mesh(mesh) = &clipped_primitive.primitive { - crate::epaint_assert!(mesh.is_valid(), "Tessellator generated invalid Mesh"); + debug_assert!(mesh.is_valid(), "Tessellator generated invalid Mesh"); } } diff --git a/crates/epaint/src/text/text_layout_types.rs b/crates/epaint/src/text/text_layout_types.rs index f0a18ba865d..dec2cb29057 100644 --- a/crates/epaint/src/text/text_layout_types.rs +++ b/crates/epaint/src/text/text_layout_types.rs @@ -890,7 +890,7 @@ impl Galley { pcursor_it.offset += row.char_count_including_newline(); } } - crate::epaint_assert!(ccursor_it == self.end().ccursor); + debug_assert!(ccursor_it == self.end().ccursor); Cursor { ccursor: ccursor_it, // clamp rcursor: self.end_rcursor(), diff --git a/crates/epaint/src/textures.rs b/crates/epaint/src/textures.rs index e4661ff02b7..4244c6871a7 100644 --- a/crates/epaint/src/textures.rs +++ b/crates/epaint/src/textures.rs @@ -49,7 +49,7 @@ impl TextureManager { pub fn set(&mut self, id: TextureId, delta: ImageDelta) { if let Some(meta) = self.metas.get_mut(&id) { if let Some(pos) = delta.pos { - crate::epaint_assert!( + debug_assert!( pos[0] + delta.image.width() <= meta.size[0] && pos[1] + delta.image.height() <= meta.size[1], "Partial texture update is outside the bounds of texture {id:?}", @@ -63,7 +63,7 @@ impl TextureManager { } self.delta.set.push((id, delta)); } else { - crate::epaint_assert!(false, "Tried setting texture {id:?} which is not allocated"); + debug_assert!(false, "Tried setting texture {id:?} which is not allocated"); } } @@ -77,7 +77,7 @@ impl TextureManager { self.delta.free.push(id); } } else { - crate::epaint_assert!(false, "Tried freeing texture {id:?} which is not allocated"); + debug_assert!(false, "Tried freeing texture {id:?} which is not allocated"); } } @@ -88,7 +88,7 @@ impl TextureManager { if let Some(meta) = self.metas.get_mut(&id) { meta.retain_count += 1; } else { - crate::epaint_assert!( + debug_assert!( false, "Tried retaining texture {id:?} which is not allocated", ); From 2b2e70cb9179eba39f1d306ff70814f68c323ba4 Mon Sep 17 00:00:00 2001 From: TicClick Date: Sat, 11 May 2024 00:07:42 +0200 Subject: [PATCH 124/134] egui-winit: emit physical key presses when a non-Latin layout is active (#4461) resolves https://github.com/emilk/egui/issues/4081 (see discussion starting from https://github.com/emilk/egui/issues/3653#issuecomment-1962740175 for extra context) this partly restores event-emitting behaviour to the state before #3649, when shortcuts such as `Ctrl` + `C` used to work regardless of the active layout. the difference is that physical keys are only used in case of the logical ones' absence now among the named keys. while originally I have only limited this to clipboard shortcuts (Ctrl+C/V/X), honestly it felt like a half-assed solution. as a result, I decided to expand this behaviour to all key events to stick to the original logic, in case there are other workflows and hotkeys people rely on or expect to work out of the box. let me know if this is an issue. --- crates/egui-winit/src/lib.rs | 14 +++++++++----- crates/egui/src/data/input.rs | 8 +++++--- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index abadd9ee5c2..4ee404e4436 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -746,15 +746,19 @@ impl State { physical_key ); - if let Some(logical_key) = logical_key { + // "Logical OR physical key" is a fallback mechanism for keyboard layouts without Latin characters: it lets them + // emit events as if the corresponding keys from the Latin layout were pressed. In this case, clipboard shortcuts + // are mapped to the physical keys that normally contain C, X, V, etc. + // See also: https://github.com/emilk/egui/issues/3653 + if let Some(active_key) = logical_key.or(physical_key) { if pressed { - if is_cut_command(self.egui_input.modifiers, logical_key) { + if is_cut_command(self.egui_input.modifiers, active_key) { self.egui_input.events.push(egui::Event::Cut); return; - } else if is_copy_command(self.egui_input.modifiers, logical_key) { + } else if is_copy_command(self.egui_input.modifiers, active_key) { self.egui_input.events.push(egui::Event::Copy); return; - } else if is_paste_command(self.egui_input.modifiers, logical_key) { + } else if is_paste_command(self.egui_input.modifiers, active_key) { if let Some(contents) = self.clipboard.get() { let contents = contents.replace("\r\n", "\n"); if !contents.is_empty() { @@ -766,7 +770,7 @@ impl State { } self.egui_input.events.push(egui::Event::Key { - key: logical_key, + key: active_key, physical_key, pressed, repeat: false, // egui will fill this in for us! diff --git a/crates/egui/src/data/input.rs b/crates/egui/src/data/input.rs index 8217f4f5a4d..2260db1f433 100644 --- a/crates/egui/src/data/input.rs +++ b/crates/egui/src/data/input.rs @@ -361,10 +361,12 @@ pub enum Event { /// A key was pressed or released. Key { - /// The logical key, heeding the users keymap. + /// Most of the time, it's the logical key, heeding the active keymap -- for instance, if the user has Dvorak + /// keyboard layout, it will be taken into account. /// - /// For instance, if the user is using Dvorak keyboard layout, - /// this will take that into account. + /// If it's impossible to determine the logical key on desktop platforms (say, in case of non-Latin letters), + /// `key` falls back to the value of the corresponding physical key. This is necessary for proper work of + /// standard shortcuts that only respond to Latin-based bindings (such as `Ctrl` + `V`). key: Key, /// The physical key, corresponding to the actual position on the keyboard. From a9efbcff360e8a6d194bc0593b84effa08ca15eb Mon Sep 17 00:00:00 2001 From: rustbasic <127506429+rustbasic@users.noreply.github.com> Date: Sat, 11 May 2024 07:09:14 +0900 Subject: [PATCH 125/134] IME for chinese (#4436) * Completed. * Closes #4430 IME for chinese --- crates/egui-winit/src/lib.rs | 42 +++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 4ee404e4436..058a27f1661 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -342,20 +342,12 @@ impl State { // We use input_method_editor_started to manually insert CompositionStart // between Commits. match ime { - winit::event::Ime::Enabled => { - self.egui_input - .events - .push(egui::Event::Ime(egui::ImeEvent::Enabled)); - self.has_sent_ime_enabled = true; + winit::event::Ime::Enabled => {} + winit::event::Ime::Preedit(_, None) => { + self.ime_event_enable(); } - winit::event::Ime::Preedit(_, None) => {} winit::event::Ime::Preedit(text, Some(_cursor)) => { - if !self.has_sent_ime_enabled { - self.egui_input - .events - .push(egui::Event::Ime(egui::ImeEvent::Enabled)); - self.has_sent_ime_enabled = true; - } + self.ime_event_enable(); self.egui_input .events .push(egui::Event::Ime(egui::ImeEvent::Preedit(text.clone()))); @@ -364,16 +356,10 @@ impl State { self.egui_input .events .push(egui::Event::Ime(egui::ImeEvent::Commit(text.clone()))); - self.egui_input - .events - .push(egui::Event::Ime(egui::ImeEvent::Disabled)); - self.has_sent_ime_enabled = false; + self.ime_event_disable(); } winit::event::Ime::Disabled => { - self.egui_input - .events - .push(egui::Event::Ime(egui::ImeEvent::Disabled)); - self.has_sent_ime_enabled = false; + self.ime_event_disable(); } }; @@ -492,6 +478,22 @@ impl State { } } + pub fn ime_event_enable(&mut self) { + if !self.has_sent_ime_enabled { + self.egui_input + .events + .push(egui::Event::Ime(egui::ImeEvent::Enabled)); + self.has_sent_ime_enabled = true; + } + } + + pub fn ime_event_disable(&mut self) { + self.egui_input + .events + .push(egui::Event::Ime(egui::ImeEvent::Disabled)); + self.has_sent_ime_enabled = false; + } + pub fn on_mouse_motion(&mut self, delta: (f64, f64)) { self.egui_input.events.push(egui::Event::MouseMoved(Vec2 { x: delta.0 as f32, From 27a22f991d9b747e031e7ffd274594facdda2a3a Mon Sep 17 00:00:00 2001 From: rustbasic <127506429+rustbasic@users.noreply.github.com> Date: Sat, 11 May 2024 07:41:02 +0900 Subject: [PATCH 126/134] Fix : In Windows, the 'egui_demo_app' screen does not appear. (#4410) * Related #4337 * Closes #4409 Fix : In Windows, the 'egui_demo_app' screen does not appear After the #4337 update. --------- Co-authored-by: Emil Ernerfeldt --- crates/eframe/src/native/epi_integration.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/eframe/src/native/epi_integration.rs b/crates/eframe/src/native/epi_integration.rs index b09f0f0e0c7..a8208a147ca 100644 --- a/crates/eframe/src/native/epi_integration.rs +++ b/crates/eframe/src/native/epi_integration.rs @@ -20,10 +20,9 @@ pub fn viewport_builder( let mut viewport_builder = native_options.viewport.clone(); - let clamp_size_to_monitor_size = viewport_builder.clamp_size_to_monitor_size.unwrap_or( - // On some Linux systems, a window size larger than the monitor causes crashes - cfg!(target_os = "linux"), - ); + // On some Linux systems, a window size larger than the monitor causes crashes, + // and on Windows the window does not appear at all. + let clamp_size_to_monitor_size = viewport_builder.clamp_size_to_monitor_size.unwrap_or(true); // Always use the default window size / position on iOS. Trying to restore the previous position // causes the window to be shown too small. From 11fa9cc7ee7535fa0047cb96da5302a3c15ce19a Mon Sep 17 00:00:00 2001 From: Varphone Wong Date: Sat, 11 May 2024 06:42:03 +0800 Subject: [PATCH 127/134] Disable interaction for `ScrollArea` and `Plot` when UI is disabled (#4457) ## Summary This PR modifies `ScrollArea` and `Plot` to disable their interactions when the UI is disabled. ## Changes - Interaction with `ScrollArea` in `egui` is disabled when the UI is disabled. - Interaction with `Plot` in `egui_plot` is disabled when the UI is disabled. - These changes ensure that `ScrollArea` and `Plot` behave consistently with the rest of the UI, preventing them from responding to user input when the UI is in a disabled state. ## Impact This PR enhances the consistency of `egui`'s UI behavior by ensuring that all elements, including `ScrollArea` and `Plot`, respect the UI's disabled state. This prevents unexpected interactions when the UI is disabled. Closes #4341 --- crates/egui/src/containers/scroll_area.rs | 1 + crates/egui_plot/src/lib.rs | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index c448269fb0c..700c2040253 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -493,6 +493,7 @@ impl ScrollArea { } = self; let ctx = ui.ctx().clone(); + let scrolling_enabled = scrolling_enabled && ui.is_enabled(); let id_source = id_source.unwrap_or_else(|| Id::new("scroll_area")); let id = ui.make_persistent_id(id_source); diff --git a/crates/egui_plot/src/lib.rs b/crates/egui_plot/src/lib.rs index 2e31f8312f5..cc721bd9cff 100644 --- a/crates/egui_plot/src/lib.rs +++ b/crates/egui_plot/src/lib.rs @@ -766,6 +766,11 @@ impl Plot { sense, } = self; + // Disable interaction if ui is disabled. + let allow_zoom = allow_zoom.and(ui.is_enabled()); + let allow_drag = allow_drag.and(ui.is_enabled()); + let allow_scroll = allow_scroll.and(ui.is_enabled()); + // Determine position of widget. let pos = ui.available_rect_before_wrap().min; // Determine size of widget. From 66d2b3ffe43f969c7b75d70423ec02dacc6ba129 Mon Sep 17 00:00:00 2001 From: rustbasic <127506429+rustbasic@users.noreply.github.com> Date: Sat, 11 May 2024 20:17:58 +0900 Subject: [PATCH 128/134] Treat `Event::PointerGone` as `PointerEvent::Released` (#4419) * Closes #4406 * Closes #4418 If `Event::PointerGone` occurs, it is treated as `PointerEvent::Released`. --- crates/egui/src/context.rs | 2 +- crates/egui/src/input_state.rs | 4 ++++ crates/egui/src/interaction.rs | 2 +- crates/egui_demo_lib/src/demo/tests.rs | 12 ++++++------ 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 04bdd16432a..2d58be7e126 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -1875,8 +1875,8 @@ impl Context { drag_started: _, dragged, drag_stopped: _, - contains_pointer, hovered, + contains_pointer, } = interact_widgets; if true { diff --git a/crates/egui/src/input_state.rs b/crates/egui/src/input_state.rs index 57a9e6477bf..22851a470b6 100644 --- a/crates/egui/src/input_state.rs +++ b/crates/egui/src/input_state.rs @@ -800,6 +800,10 @@ impl PointerState { } Event::PointerGone => { self.latest_pos = None; + self.pointer_events.push(PointerEvent::Released { + click: None, + button: PointerButton::Primary, + }); // NOTE: we do NOT clear `self.interact_pos` here. It will be cleared next frame. } Event::MouseMoved(delta) => *self.motion.get_or_insert(Vec2::ZERO) += *delta, diff --git a/crates/egui/src/interaction.rs b/crates/egui/src/interaction.rs index 8a7b2d0948c..e25e1c4aa13 100644 --- a/crates/egui/src/interaction.rs +++ b/crates/egui/src/interaction.rs @@ -283,7 +283,7 @@ pub(crate) fn interact( drag_started, dragged, drag_stopped, - contains_pointer, hovered, + contains_pointer, } } diff --git a/crates/egui_demo_lib/src/demo/tests.rs b/crates/egui_demo_lib/src/demo/tests.rs index 44e355d0e8e..6a8348ac56a 100644 --- a/crates/egui_demo_lib/src/demo/tests.rs +++ b/crates/egui_demo_lib/src/demo/tests.rs @@ -466,23 +466,23 @@ fn response_summary(response: &egui::Response, show_hovers: bool) -> String { // These are in inverse logical/chonological order, because we show them in the ui that way: if response.triple_clicked_by(button) { - writeln!(new_info, "Triple-clicked{button_suffix}").ok(); + writeln!(new_info, "Triple_clicked_by{button_suffix}").ok(); } if response.double_clicked_by(button) { - writeln!(new_info, "Double-clicked{button_suffix}").ok(); + writeln!(new_info, "Double_clicked_by{button_suffix}").ok(); } if response.clicked_by(button) { - writeln!(new_info, "Clicked{button_suffix}").ok(); + writeln!(new_info, "Clicked_by{button_suffix}").ok(); } if response.drag_stopped_by(button) { - writeln!(new_info, "Drag stopped{button_suffix}").ok(); + writeln!(new_info, "Drag_stopped_by{button_suffix}").ok(); } if response.dragged_by(button) { - writeln!(new_info, "Dragged{button_suffix}").ok(); + writeln!(new_info, "Dragged_by{button_suffix}").ok(); } if response.drag_started_by(button) { - writeln!(new_info, "Drag started{button_suffix}").ok(); + writeln!(new_info, "Drag_started_by{button_suffix}").ok(); } } From e06b225dabd763f7247ff4cab6600737f3327bc6 Mon Sep 17 00:00:00 2001 From: Avery Radmacher <45777186+avery-radmacher@users.noreply.github.com> Date: Sat, 11 May 2024 10:48:12 -0400 Subject: [PATCH 129/134] Fix: Window position creeps between executions on scaled monitors (#4443) * Closes * Refactors active monitor detection so it can be called from multiple locations. Compare this gif to the one on the issue report. ![egui_window_position_no_creep](https://github.com/emilk/egui/assets/45777186/8e05d4fb-266e-48b9-9223-b65f16500a99) ### Investigation notes - [`WindowSettings.inner_position_pixels` and `WindowSettings.outer_position_pixels`](https://github.com/emilk/egui/blob/master/crates/egui-winit/src/window_settings.rs#L8-L12) are stored in physical/pixel coordinates. - `ViewportBuilder::with_position` expects to be passed a position in _logical_ coordinates. - Prior to this PR, the position was being passed from `WindowSettings` to `with_position` [without any scaling](https://github.com/emilk/egui/blob/master/crates/egui-winit/src/window_settings.rs#L61-L68). This was the root cause of the issue. - The fix is to first convert the position to logical coordinates, respecting the scaling factor of the active monitor. This requires us to first determine the active monitor, so I factored out some of the logic in [`clamp_pos_to_monitor`](https://github.com/emilk/egui/blob/master/crates/egui-winit/src/window_settings.rs#L130) to find the active monitor. --- crates/eframe/src/native/epi_integration.rs | 6 +++- crates/egui-winit/src/window_settings.rs | 39 +++++++++++++++++---- 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/crates/eframe/src/native/epi_integration.rs b/crates/eframe/src/native/epi_integration.rs index a8208a147ca..827b9ec249c 100644 --- a/crates/eframe/src/native/epi_integration.rs +++ b/crates/eframe/src/native/epi_integration.rs @@ -38,7 +38,11 @@ pub fn viewport_builder( } window_settings.clamp_position_to_monitors(egui_zoom_factor, event_loop); - viewport_builder = window_settings.initialize_viewport_builder(viewport_builder); + viewport_builder = window_settings.initialize_viewport_builder( + egui_zoom_factor, + event_loop, + viewport_builder, + ); window_settings.inner_size_points() } else { if let Some(pos) = viewport_builder.position { diff --git a/crates/egui-winit/src/window_settings.rs b/crates/egui-winit/src/window_settings.rs index c59a0f451ce..ec633d3df0a 100644 --- a/crates/egui-winit/src/window_settings.rs +++ b/crates/egui-winit/src/window_settings.rs @@ -50,8 +50,10 @@ impl WindowSettings { self.inner_size_points } - pub fn initialize_viewport_builder( + pub fn initialize_viewport_builder( &self, + egui_zoom_factor: f32, + event_loop: &winit::event_loop::EventLoopWindowTarget, mut viewport_builder: ViewportBuilder, ) -> ViewportBuilder { crate::profile_function!(); @@ -64,7 +66,15 @@ impl WindowSettings { self.outer_position_pixels }; if let Some(pos) = pos_px { - viewport_builder = viewport_builder.with_position(pos); + let monitor_scale_factor = if let Some(inner_size_points) = self.inner_size_points { + find_active_monitor(egui_zoom_factor, event_loop, inner_size_points, &pos) + .map_or(1.0, |monitor| monitor.scale_factor() as f32) + } else { + 1.0 + }; + + let scaled_pos = pos / (egui_zoom_factor * monitor_scale_factor); + viewport_builder = viewport_builder.with_position(scaled_pos); } if let Some(inner_size_points) = self.inner_size_points { @@ -127,12 +137,12 @@ impl WindowSettings { } } -fn clamp_pos_to_monitors( +fn find_active_monitor( egui_zoom_factor: f32, event_loop: &winit::event_loop::EventLoopWindowTarget, window_size_pts: egui::Vec2, - position_px: &mut egui::Pos2, -) { + position_px: &egui::Pos2, +) -> Option { crate::profile_function!(); let monitors = event_loop.available_monitors(); @@ -142,7 +152,7 @@ fn clamp_pos_to_monitors( .primary_monitor() .or_else(|| event_loop.available_monitors().next()) else { - return; // no monitors 🤷 + return None; // no monitors 🤷 }; for monitor in monitors { @@ -159,6 +169,23 @@ fn clamp_pos_to_monitors( } } + Some(active_monitor) +} + +fn clamp_pos_to_monitors( + egui_zoom_factor: f32, + event_loop: &winit::event_loop::EventLoopWindowTarget, + window_size_pts: egui::Vec2, + position_px: &mut egui::Pos2, +) { + crate::profile_function!(); + + let Some(active_monitor) = + find_active_monitor(egui_zoom_factor, event_loop, window_size_pts, position_px) + else { + return; // no monitors 🤷 + }; + let mut window_size_px = window_size_pts * (egui_zoom_factor * active_monitor.scale_factor() as f32); // Add size of title bar. This is 32 px by default in Win 10/11. From 3b3ce22adc48f9bc362dd546e8c2633d314e68fb Mon Sep 17 00:00:00 2001 From: rustbasic <127506429+rustbasic@users.noreply.github.com> Date: Sat, 11 May 2024 23:49:27 +0900 Subject: [PATCH 130/134] Make sure plot size is positive (#4429) * Closes #4425 Fix: in Plot, Minimum values for screen protection. --- crates/egui_demo_lib/src/demo/context_menu.rs | 2 ++ crates/egui_plot/src/lib.rs | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/egui_demo_lib/src/demo/context_menu.rs b/crates/egui_demo_lib/src/demo/context_menu.rs index 1b71eb4f787..5195a2b0609 100644 --- a/crates/egui_demo_lib/src/demo/context_menu.rs +++ b/crates/egui_demo_lib/src/demo/context_menu.rs @@ -97,11 +97,13 @@ impl super::View for ContextMenus { egui::Grid::new("button_grid").show(ui, |ui| { ui.add( egui::DragValue::new(&mut self.width) + .clamp_range(0.0..=f32::INFINITY) .speed(1.0) .prefix("Width: "), ); ui.add( egui::DragValue::new(&mut self.height) + .clamp_range(0.0..=f32::INFINITY) .speed(1.0) .prefix("Height: "), ); diff --git a/crates/egui_plot/src/lib.rs b/crates/egui_plot/src/lib.rs index cc721bd9cff..6b9d622d6e3 100644 --- a/crates/egui_plot/src/lib.rs +++ b/crates/egui_plot/src/lib.rs @@ -742,7 +742,7 @@ impl Plot { margin_fraction, width, height, - min_size, + mut min_size, data_aspect, view_aspect, mut show_x, @@ -773,6 +773,10 @@ impl Plot { // Determine position of widget. let pos = ui.available_rect_before_wrap().min; + // Minimum values for screen protection + min_size.x = min_size.x.at_least(1.0); + min_size.y = min_size.y.at_least(1.0); + // Determine size of widget. let size = { let width = width From 4f2f0575082c4d7ce99b085ff2c4a0ff6735e12d Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 11 May 2024 20:01:40 +0200 Subject: [PATCH 131/134] Update arboard (#4482) Updating arboard v3.3.1 -> v3.4.0 Updating clipboard-win v5.1.0 -> v5.3.1 --- Cargo.lock | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4466ae0f344..e751dd77f23 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -199,17 +199,16 @@ checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "arboard" -version = "3.3.1" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1faa3c733d9a3dd6fbaf85da5d162a2e03b2e0033a90dceb0e2a90fdd1e5380a" +checksum = "9fb4009533e8ff8f1450a5bcbc30f4242a1d34442221f72314bea1f5dc9c7f89" dependencies = [ "clipboard-win", "log", - "objc", - "objc-foundation", - "objc_id", + "objc2 0.5.1", + "objc2-app-kit", + "objc2-foundation", "parking_lot", - "thiserror", "x11rb", ] @@ -797,9 +796,9 @@ checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" [[package]] name = "clipboard-win" -version = "5.1.0" +version = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ec832972fefb8cf9313b45a0d1945e29c9c251f1d4c6eafc5fe2124c02d2e81" +checksum = "79f4473f5144e20d9aceaf2972478f06ddf687831eafeeb434fbaf0acc4144ad" dependencies = [ "error-code", ] @@ -1146,7 +1145,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "libloading 0.8.0", + "libloading 0.7.4", ] [[package]] @@ -1958,7 +1957,7 @@ dependencies = [ "bitflags 2.5.0", "com", "libc", - "libloading 0.8.0", + "libloading 0.7.4", "thiserror", "widestring", "winapi", @@ -4321,7 +4320,7 @@ dependencies = [ "js-sys", "khronos-egl", "libc", - "libloading 0.8.0", + "libloading 0.7.4", "log", "metal", "naga", From 059218d954cfbe272e1958a91dfecb701abed7a1 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 11 May 2024 20:17:06 +0200 Subject: [PATCH 132/134] eframe: Remove dependency on `thiserror` (#4483) Less dependencies => faster compile times * Part of https://github.com/emilk/egui/issues/4481 --- Cargo.lock | 1 - crates/eframe/Cargo.toml | 1 - crates/eframe/src/lib.rs | 99 +++++++++++++++++++++++++++++++++++----- 3 files changed, 87 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e751dd77f23..198494929e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1203,7 +1203,6 @@ dependencies = [ "ron", "serde", "static_assertions", - "thiserror", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", diff --git a/crates/eframe/Cargo.toml b/crates/eframe/Cargo.toml index c5cfae87733..33cc5ba6bfe 100644 --- a/crates/eframe/Cargo.toml +++ b/crates/eframe/Cargo.toml @@ -134,7 +134,6 @@ log.workspace = true parking_lot.workspace = true raw-window-handle.workspace = true static_assertions = "1.1.0" -thiserror.workspace = true web-time.workspace = true # Optional dependencies diff --git a/crates/eframe/src/lib.rs b/crates/eframe/src/lib.rs index 98576358a70..e1df19a8b0b 100644 --- a/crates/eframe/src/lib.rs +++ b/crates/eframe/src/lib.rs @@ -331,37 +331,112 @@ pub fn run_simple_native( // ---------------------------------------------------------------------------- /// The different problems that can occur when trying to run `eframe`. -#[derive(thiserror::Error, Debug)] +#[derive(Debug)] pub enum Error { /// An error from [`winit`]. #[cfg(not(target_arch = "wasm32"))] - #[error("winit error: {0}")] - Winit(#[from] winit::error::OsError), + Winit(winit::error::OsError), /// An error from [`winit::event_loop::EventLoop`]. #[cfg(not(target_arch = "wasm32"))] - #[error("winit EventLoopError: {0}")] - WinitEventLoop(#[from] winit::error::EventLoopError), + WinitEventLoop(winit::error::EventLoopError), /// An error from [`glutin`] when using [`glow`]. #[cfg(all(feature = "glow", not(target_arch = "wasm32")))] - #[error("glutin error: {0}")] - Glutin(#[from] glutin::error::Error), + Glutin(glutin::error::Error), /// An error from [`glutin`] when using [`glow`]. #[cfg(all(feature = "glow", not(target_arch = "wasm32")))] - #[error("Found no glutin configs matching the template: {0:?}. Error: {1:?}")] NoGlutinConfigs(glutin::config::ConfigTemplate, Box), /// An error from [`glutin`] when using [`glow`]. #[cfg(feature = "glow")] - #[error("egui_glow: {0}")] - OpenGL(#[from] egui_glow::PainterError), + OpenGL(egui_glow::PainterError), /// An error from [`wgpu`]. #[cfg(feature = "wgpu")] - #[error("WGPU error: {0}")] - Wgpu(#[from] egui_wgpu::WgpuError), + Wgpu(egui_wgpu::WgpuError), +} + +impl std::error::Error for Error {} + +#[cfg(not(target_arch = "wasm32"))] +impl From for Error { + #[inline] + fn from(err: winit::error::OsError) -> Self { + Self::Winit(err) + } +} + +#[cfg(not(target_arch = "wasm32"))] +impl From for Error { + #[inline] + fn from(err: winit::error::EventLoopError) -> Self { + Self::WinitEventLoop(err) + } +} + +#[cfg(all(feature = "glow", not(target_arch = "wasm32")))] +impl From for Error { + #[inline] + fn from(err: glutin::error::Error) -> Self { + Self::Glutin(err) + } +} + +#[cfg(feature = "glow")] +impl From for Error { + #[inline] + fn from(err: egui_glow::PainterError) -> Self { + Self::OpenGL(err) + } +} + +#[cfg(feature = "wgpu")] +impl From for Error { + #[inline] + fn from(err: egui_wgpu::WgpuError) -> Self { + Self::Wgpu(err) + } +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + #[cfg(not(target_arch = "wasm32"))] + Self::Winit(err) => { + write!(f, "winit error: {err}") + } + + #[cfg(not(target_arch = "wasm32"))] + Self::WinitEventLoop(err) => { + write!(f, "winit EventLoopError: {err}") + } + + #[cfg(all(feature = "glow", not(target_arch = "wasm32")))] + Self::Glutin(err) => { + write!(f, "glutin error: {err}") + } + + #[cfg(all(feature = "glow", not(target_arch = "wasm32")))] + Self::NoGlutinConfigs(template, err) => { + write!( + f, + "Found no glutin configs matching the template: {template:?}. Error: {err}" + ) + } + + #[cfg(feature = "glow")] + Self::OpenGL(err) => { + write!(f, "egui_glow: {err}") + } + + #[cfg(feature = "wgpu")] + Self::Wgpu(err) => { + write!(f, "WGPU error: {err}") + } + } + } } /// Short for `Result`. From c3f386aa301f26106397c4e14434bd5a734ba6b6 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 11 May 2024 20:17:19 +0200 Subject: [PATCH 133/134] Remove work-around for `unsafe` in puffin macro (#4484) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …since it is no longer in the puffin macro --- crates/egui/src/lib.rs | 2 -- crates/egui_demo_lib/src/lib.rs | 2 -- crates/egui_extras/src/lib.rs | 2 -- crates/emath/src/lib.rs | 2 -- crates/epaint/src/lib.rs | 2 -- 5 files changed, 10 deletions(-) diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index a846acc701e..b367158a58b 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -371,8 +371,6 @@ #![allow(clippy::float_cmp)] #![allow(clippy::manual_range_contains)] -#![cfg_attr(feature = "puffin", deny(unsafe_code))] -#![cfg_attr(not(feature = "puffin"), forbid(unsafe_code))] mod animation_manager; pub mod containers; diff --git a/crates/egui_demo_lib/src/lib.rs b/crates/egui_demo_lib/src/lib.rs index 7dba93b4117..68bdc8eed85 100644 --- a/crates/egui_demo_lib/src/lib.rs +++ b/crates/egui_demo_lib/src/lib.rs @@ -10,8 +10,6 @@ #![allow(clippy::float_cmp)] #![allow(clippy::manual_range_contains)] -#![cfg_attr(feature = "puffin", deny(unsafe_code))] -#![cfg_attr(not(feature = "puffin"), forbid(unsafe_code))] mod demo; pub mod easy_mark; diff --git a/crates/egui_extras/src/lib.rs b/crates/egui_extras/src/lib.rs index e899106cd45..381242c90b5 100644 --- a/crates/egui_extras/src/lib.rs +++ b/crates/egui_extras/src/lib.rs @@ -8,8 +8,6 @@ #![allow(clippy::float_cmp)] #![allow(clippy::manual_range_contains)] -#![cfg_attr(feature = "puffin", deny(unsafe_code))] -#![cfg_attr(not(feature = "puffin"), forbid(unsafe_code))] #[cfg(feature = "chrono")] mod datepicker; diff --git a/crates/emath/src/lib.rs b/crates/emath/src/lib.rs index 4ac46f21947..6ad48bd2543 100644 --- a/crates/emath/src/lib.rs +++ b/crates/emath/src/lib.rs @@ -20,8 +20,6 @@ //! #![allow(clippy::float_cmp)] -#![cfg_attr(feature = "puffin", deny(unsafe_code))] -#![cfg_attr(not(feature = "puffin"), forbid(unsafe_code))] use std::ops::{Add, Div, Mul, RangeInclusive, Sub}; diff --git a/crates/epaint/src/lib.rs b/crates/epaint/src/lib.rs index db48fce6876..d5e7055813b 100644 --- a/crates/epaint/src/lib.rs +++ b/crates/epaint/src/lib.rs @@ -22,8 +22,6 @@ #![allow(clippy::float_cmp)] #![allow(clippy::manual_range_contains)] -#![cfg_attr(feature = "puffin", deny(unsafe_code))] -#![cfg_attr(not(feature = "puffin"), forbid(unsafe_code))] mod bezier; pub mod color; From acfe9f6f3b3eaff9a603c1568bd590948db52329 Mon Sep 17 00:00:00 2001 From: crumblingstatue Date: Mon, 13 May 2024 12:49:31 +0200 Subject: [PATCH 134/134] Make `epaint::mutex::RwLock` allow `?Sized` types (#4485) `parking_lot`'s `RwLock` allows this, so probably `epaint`'s `RwLock` should too. Although I'm not sure how much it's intended for users, rather than just internal use by `egui`. --- crates/epaint/src/mutex.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/epaint/src/mutex.rs b/crates/epaint/src/mutex.rs index 2c61f038f9c..157701c2be0 100644 --- a/crates/epaint/src/mutex.rs +++ b/crates/epaint/src/mutex.rs @@ -133,14 +133,16 @@ mod rw_lock_impl { /// the feature `deadlock_detection` is turned enabled, in which case /// extra checks are added to detect deadlocks. #[derive(Default)] - pub struct RwLock(parking_lot::RwLock); + pub struct RwLock(parking_lot::RwLock); impl RwLock { #[inline(always)] pub fn new(val: T) -> Self { Self(parking_lot::RwLock::new(val)) } + } + impl RwLock { #[inline(always)] pub fn read(&self) -> RwLockReadGuard<'_, T> { parking_lot::RwLockReadGuard::map(self.0.read(), |v| v)