From e22979c1c7042943594e86f86fe921426608d380 Mon Sep 17 00:00:00 2001 From: Thomas Peterson Date: Tue, 10 Feb 2026 11:54:01 +0100 Subject: [PATCH] Fixes --- ...oomCardWidgetBase.cpp.E82CB8390DB7EE04.idx | Bin 7860 -> 7954 bytes .../WidgetConfig.hpp.CAEFE2EEEB2A6996.idx | Bin 20476 -> 21764 bytes main/WidgetConfig.cpp | 40 ++++-- main/WidgetConfig.hpp | 32 ++++- main/WidgetManager.cpp | 134 ++++++++++++++---- main/widgets/RoomCardWidgetBase.cpp | 16 ++- sdkconfig | 8 +- .../src/components/SettingsModal.vue | 22 +++ .../widgets/elements/RoomCardElement.vue | 24 +++- .../widgets/settings/RoomCardSettings.vue | 21 ++- web-interface/src/stores/editor.js | 7 + 11 files changed, 234 insertions(+), 70 deletions(-) diff --git a/.cache/clangd/index/RoomCardWidgetBase.cpp.E82CB8390DB7EE04.idx b/.cache/clangd/index/RoomCardWidgetBase.cpp.E82CB8390DB7EE04.idx index 428d613ba7d0c4a2a46090e648c60910188d0827..41e85980e4266e29379da428475fa9262a1fb43e 100644 GIT binary patch delta 2898 zcmY*bdr(x@9p;M{xWEM#gx$Te>+*i@@(^@DG>8!}#!zi0H8V9YYtl9~sn{Tlk8L4w zVj7@$SfSJ4fNT{<$rPe=@C7OuTVAeqhHdvT9bjg|7FkWClcY_j=aL*b@XvC-^E=<~ z`+dK2?l~7XoY)w!b8Bg7u%Rd5T%b@7xt@F)_@OWYd4y~d99Q zJ&=3IefrN7V>8k@$ddQuzT~#xIao`xCFx` zq%$Dk#&305g)fVsGgly7A)f>%6}Ka;9cx@Q*I!sa^yV;~8AsYU)^zXd-14Wv?MEr# zMZOnLS2$uTZ{&!B>fJf1V zkGviFL6M^2`!@K!+V{NOaBRk?lyO)({6rfaUVwA~dd|Z>2z?|jtTfsItplsMTrL<~ znkM2_9SE>OutHDvM`Y69d@JdQPz!Ys>&TUe*LJ-R@qMVdE}uf; zDdcjwtypbECIfV7_gnw>hsd{-ZCS0a&SWcJub7xCO8w}U3f_vMR`RRJq{M2eI*J8Q zdUB&P9BY4d#0I*VDuG{h_04+ThH&8m^jY2-8q61B}yTdz48yjo{P79Hrh`hk`n6WtRNz z%(ahh|K58<@yv~QH%yG4NqdTZb(C)%GAF0k9^Soj)4MeE0iqrdchpbJ4GaN0|A8QlnPG!y zLo~P83DJpYR>fjjqI((OgXDwE*i@gd-K%@F{X@lpT8OpKF>fyacXLf@)7eJlOXNVj zW62u|xRB;TJ}Fj5nI}*%fi0W_8}e)@W`MYmn&N$>nJ6@q$e2VvCvFPaQz&FwUfOQE zs=u+*qG&lH9g&%mi+8HOZfLDs#2v_XAdgYdEmpj^$CS%Kxrva-+VqwCXDRLH_O&T_ zs}ic#Ffuyq!tO5YV?7~Wb*y;>U7xN^k7uiC)FJ5*e=5&IIuA8l!~`gLm5UGQY~{+~ zlJEmv~~R2_(A32EqO5I&>Zc44~~Y9D*fn! zu?s~^%_30Ng%=qR8c-!W{oWZ@F3V&gIqX$q{+ZhgJ0v>nMNltRvW8Kh59&U|GJs^R zDmPnDXhAv4OJhzU;uO6(mp-?{h!}=}t85f`qbOPCe&H<2nQvsnO_wY9;kZ(j6Pul= zV731^?ReJubHD!kch3`P0+HR%EMkqt2>`W7)ZlXqg$ixm*=6_H z1+2aRBNM{N>B%OYx*S1ysS>-+RB|x+V9t}t69qRn{pFkgC~8|FwIYQj@T=VWi`SHg c)Pc%92i~T?E5?xh*wGSp{-^Dqmg)E5|CT(Dp#T5? delta 2811 zcmY*bZBSg*8RpTuuq>Bk`PjXCVK>Y7$HFcNLMY@zAPFIDk%3ypapDJQ;!LZZhKzvu zC*`dFbM5fQ*Dh2PHTUDGq%mW*F))Y#$*!G(09q8}z@wn*9e3 z9Ux<&51H$e>ho_NS-7-aGJN%?uS;*-{PuP*bW^x{yj@qB)d!^y3D3V3GO(j9zE_?? zKdk+*aUl5Wi{`9?C$yj|Z$Ns34C;~#yO7?62j*Y6^6-{x&v@v{DAGsqz`%1oyDqxR zeocV|@wLMelmlJ{(s$qo_h14IqPr! zVrMFj$VWsK7a(0AVe3i^UYNYtAW-{Z^^*hZt~9hj+k*96AEju;`c@bPzzMSxi2~4t z_%5UgrF0|04U>?`DB?$vCIDXOy-I*rbtA7E+euMGCXL8-k%JLNsR`O9@?yl(RmY({4x`W?ok;3LHs_(+UD)74 zCI`&vAD{jHcagtf#C4XsOunvP4m1&2uwo90 zbL4cSL-HffPevkJ8k(VLh9czG0Zj)KemW)Tg{Bt@A5sboKr;ZPv;4}rbGJv_6c~bL z2+FPWu%3r&)6Y_17@A=yU;cb9_@%vlUsGTNnh_|1(G$>2u)#|{ky^6^ZS!@^c(2xYdtUsn|;vwVBlHYO-nny z23Ur?43)F#|F(I*`DWQE=0GE~jWBa>-uZ6F3u!OE_7Zy?c#+_}=M4q?NcSU`=#9}0 zW5^%FEpK)aHbdgDnaVKfuSm_V)|ZU!4?P{6f3R_4EKxmx)O({fZks&XZl z-mm|6%h{T{xEC9}$l(;U#TxGR82hZSZy1tznS2Mo-22*zmUE0uw#)e&i>%LX`p5UO z%iXh&d~@a;E3D0AOW^S|?67=zbq@InjF^2BVdH^Op42M^i~D*QaR9p-0(guk@aC=K zo%ugBpJz)hl)6yC3mH`H#V^V2EtE~Ix6EwtS=sMnA#s2GQKoD`UnQ=CY0 zB3tNTH&i!Lh0uA#&65)*hvY$mhrDHS(6NedLLL`Dm9tv|vbbklFmsb#Bap~fHcWr= zVbvF^hgr6aEz88A8mmu2d-7f*(mm|P0XyGALoY+R43m(~b!e}{%rzR*;;(nzdi?`t z_aurZQN`;?fmsyKl0G#d=kZU1%D$OOonU8oV^=rIxM7V??mqvMtKpMur44c$Qn(!? zJSNuAhtNJm@j}s<3ou?F+hQuKohWdkln2w5PDFIV%6Td9HX`1JRZ#9mjvK{XIgOY{ z!8}TNJCJRo^Ygl|K4MaQ*x@5LV~iDJkjD@y>{*0IoMff?$5P40tOe$W z4+%bG3w|skWtmjPIwS%+A*W((aqKKxqAi2ZO55(a&mEud_>AF8$X+71V^3FV=tH75 zQF+Rq+c&;aA=AfW^08Rn1N4y&>??>B%xFVu8#W6-JJQ;b#{sfp-YT&>CCRloifS`! zR~rgv@<)q4`1U_cKs)4ir1CvA`=5Q9U0@E?>^t;J`X5th`e~nTPeZ+VsMqow{tFz6 BV`u;X diff --git a/.cache/clangd/index/WidgetConfig.hpp.CAEFE2EEEB2A6996.idx b/.cache/clangd/index/WidgetConfig.hpp.CAEFE2EEEB2A6996.idx index 6514f2079b8cbd95ab3fed85c0a3a014aa3afb26..34fe364cd51f8ebe17e61ce34f473326064a3d7e 100644 GIT binary patch literal 21764 zcmY*g1zeTO690Bx#lt!0JEWk37?{`{*G??#PVBgLcYv)Z7%14-g@RyR6>PD)u@$hd zJX_z+x95LFfA8(wGc(_8&Cbs5{%PS~uip1onpV4~|Cqmfjp!~kO>>q1A3kDOzo8DA zcEd%}#zdZP`+CrmeeTcJZ$4UlAsh8L#P?v!wH>z?Ewdm;`(gQehraTey0mYEx4&D1 zaV~FLrf2`!Gjny{%S|sQe>?WL(#&OUV@JNtZ{-1(Ms;-!INh?zq0qhVOBV0VeY#e3 z*2MbJ4V)X^j4K>CCS+V>-E9scTR_$gv;V%q&aU%_zE$7)AgtWBS#u})6kX6_Px(UW zRXRQXmOIEaEqhj#HAENgz|j@7k*uWa~3pMW&$ zn8sbMUF;a>e`wpYEMsGCr;n)ot?cGmq&D-bNUadpED=%|b9ComD zpYF2`-A(CJy=%axmfN;AiC)?E(WK^AyEoW!syBY^$}n_jE_s z@Eu(o4oqEscFLkn7l)L+usWYxHH%x#CleZVIobW|iJ{R`O3ixY@-k?C_f=^#t>z_d z=C*d;G2Ojrc{Z+4V0hnhjl+5ctt)$`+&=*^Bd5&h;yPge^trz6j_v-{uY2m0Qq9xm ztQQLgMo(C<)Nj_X=%&m1SM4_~*PQI_o{YZZm2+3s@*NYG982!Iw_uAYzMpgKSR4{o zwz;k6=+K96N9+m=C>^jka$&ZpuHD-dz53#K?$_G<#{XP-*Dq7-__t3!-dUK zEBQr4(GCu`{Z4nAHLa(6?QZq@^gEzWA7~o3xzmQmttL7?emQzy(GM?2XZ=vIL#-Gs zQ@W$2Y?$ZwSXRlgncFn4Ud<-n`(Cd8kiP@ZBz&FQVNmpg%|*IYIZ%Dr_Ov_Kr(R$3 zZb;`(O}|~s*>FL}u((`<`t^AAY)ihx4x-rKm$u~dxb|sBw*j*n&8XJ-Y5mO~Gwr-} z_sfUvw*sCzRh=@_e?;9u(I11J#@;t~UNc}%m}}Ed4k;5)r8nQvF3`VGK0Gm@ z7NTw`QK<8dz;>HmGi}J3US|2)DQC=`#}Bv@`YY}Im%rZ*{kz1u+i9~)760pG)z*7I zMS54UwRJC8t8dzxjo(JqIlOCVYNXNYyv>H;;&Gd$T}#jhxsv{Y!1ijh)?k(&nUA7q8rJ>Us71(FcdO{?+Q~mJJ=QC6=re zGycZe$e%TjW*a#4OOuJS;z}2|`=fotn^BntCfwLxvBRN+4_e%}*kk#Z6pY>x;x#b) zuB`F@_Wf(bfTfS-@xEH*!jgBjcJJOBARCHogH?LQ#o_4sbX*NuqZI;3gE{E1`! z*SKQbm8@@)`cJD=X#UW*P6O_=T)SrBsx^}r)|wXTT6Fg9CHdtS^Wvl8u05QSHL>u{ z)|N$WHnh$>Dz?DSo~0{A&uIK~YPoiecg}u!t?A1_d9!aj5!ABzq$A5G7g+bLaq9g# zdt>gVeMyfE4|p*;=Jv;Bw(`p&<9bcpemd&=(8HgH9u5zGAJ*dZ*1QL+pQzl|@0TU5 znM3-oPW4aSK61K(e}T}fl_Dk#NcJmoAjj$g){U=^7wdR)=FUu2?zAc3S2p%UzqU{Q z(GDICe^qfnuG3X+P0!T4Ew9nYeXx5<_=i*3TaJ9zsc*6Px_x{%_6V+WU*G>cbXl4I zZC&2QWryaTXF&dq#U4#@9A13N-~y+p%^Y^Ds7&h*C z@&E0-w)f@w%k$^{E}ML{GDyqa&^ykV<%hW`*^}Fyh`R~TY~&@`wrUn+H|LM&y*E$_5JGv zGVs2aF{l(w6YBf+BKKAcG$AX#` zT7J{i2bT*loiF6>S~BZq>1I`>mQ`-h@?Y!k1=0(r`ef4iK|V@qoaWA_xhE*JM5hT| z^DHVPwYISJOsYk3OB8njg<3++KFbz=tt)I=u??h^O=QceO3fteiehIOOJm4=e@QA4SXPh002`H$vm- z&8d=4qtSg^ndZUNhWnTCJ5dNex9qSs9h2Rxy&D zG1OwpPcHw+Z?n73pE1LAY~7rs>&Oz-O+RofZI#FCY&NZ~VW^pCCN-!eLsej?p1WJU zXP&h^GHa2>NYppicTZ+ilG-x+C~RJKu1xCwc^+1+DYZSTXV!eG{G;u6Tyr&Ax%sPy zO$%X$hj|DOb26jS(1V9gK%P0vb0o0Z!?VwOit4!@#M8`Upj&F3+fayjuuG6)#| zlG!zGx+a5$-M4n%?XzY^!sOg?Wh^JJX)oH#r9q`J@Zs#V;Wd|IUZ1YBBRH?lFRNh) z=C)v3XNtnwl~)_a>{wMhpUxwV4!+`+S3GAjQxsZVmd+`8Y1&(<6=cMw3*2&ndn7YO zp>=0?;egun6Mb!3ml3jIPq=u(rB5hoG;a8DqiqYl3br}#!}8m-+J?XS3xDaaRFYOD z`0LLf1v4EfxuAee%kL@ejSwSbG@+8T_HmB{!&r+!BTIU8xmQ5v*HuRsbJxY(JDDkp z#(frLznrbtu3#2ElHB?zcR0#r2vd{{4pl+GG0MGHo=g1-*|Z`QQ)-D?vI|ri9k9og zU(stKv*u|~Sl80nESiKE<`_e>sVM4v+vB)*aLdrbI^Uxv{U)>Bq`;{t)bdZQ9Lw%z zDPq%l8Z+8abTlP1DvdfZqlH>8dRFAd~lm!eQh z9`ia&!`>@P>HNI17R0SV+$)(W3bp&Y=SNKG*0!|Hf6369%-(V9JMIGtt%pfd$E~>0 zuCzP%p_nn3dCz49K%o@*wYT+gDaSIpwu(=oDOk*07c;qEDhjpguWD;KJsXtK`C8Tf z72L9d8m1`J0xs@Oo%F1oRJ*9!KZ85Y;BuEx6l$Y3J#;M5VT5`k0Fj*g!RNr1Kxq1I&S^YY+U`1U^Rqh|l9AjCw zWTq&L<>)bH`pe^gRn+-RBWj%EmUFb7DhjQ>M@k$zxUGpaHiuL2Ph#dICbblW*0!xz zI=wEwrJ_w+p!OS2;VBm;mBvtDzd5-qXF}%4_Z2e}H<^_*mz2$-($G>%{7leaHM^b+ zsjTxDHD1q|<8#VX6h)O6C$t#z>C~{lY+7FmXAik}NE4)}Va^`TCb~>3Tg8&CnhU>0 z_Ur>2&pqRLf&Yx1^6dVycHJ6OCpmQxa}1&?6@^x*5&^M|!{61^d6*H6KXTWPJWn!H z6k6Z9Z=5vQaZxQ@+iz@_=b7a^lL1swXyq(?Vra=_Yin7x<}?yco=*8yNljlZH%*m< z+^l3?RlIf2I<%`&+L^#D3DiwRVeDFmezvv2S?Xq%sUul)F}uiUM`ea_{Wvzyge&8J z)YY|(EP|Hgab`JAJFlX!)%beJLZ0jE)ytyIG;D2W4%^9j6eWApB%czpgq_WMc=gCz zU*|v6M15e^4@^3?qA-}~?2&hBrmgjDS{R!{^+qre!5l!17S3j;jI}aol*8F~H7ff% z@hiWV<`r(b!gZK?x8(TAEqTWK>wKA7ns1o%8(QRwLha;~;ZxSf6mP6+3%Icd>}A>Z zGP%Jj3bo037rrhYv#MzpZH3X>!_46@?G%bq+m_mUyK>XBV}8w#H`Dz@;)%RHC*k}B7nx&sC7TH?58nS6tyN~xmq||7p;X&vl?4U2hl;M##9={u%#qy zUU#@MAKF63Gu73F+_aEpUr|`o7Caa*B*(%Qx)!2hZ5XqJQKgDP?RL?6QIBesXlc~~ z$VVJK9sN{k)Cq?>6CZG9c~GNPI$x*i4Cj_`%DfbXG1I<*xhGrhw6baSjr`JGxXT%( z(y)g0KJiUeub;~|x7PW5W$hO;|DsJ%QCK_UR<-x3e|+26v}#7i5g-C&jZ~7gvdB34 zf1lkr>zJo)ZCXu3t%+zN*C>^wRsm||i=>|q8BnRcuDwu>euP<%(3wI}RNBF_@3fOW z_qEsg4&}ZZxoIPpbFV1W{?cm4Svp5{v}u_Q?_I;>*+*u-iW8$g&s_O5|@}0>pD~hV!+SZw~F>Q2rn^wct9hct;5Tk+FJK+y>u;BIr<*vxQBdBQK)S> zx7=;y)VIBCnxI8fNEDKOPNmT_`=a5~B8!)d&)M6i^`~Kbz{LYD+oq_YXdku-4=y#V z{Rc7UU8sJ``b}k zDlJOO&5uf>^`^td40LMnZBfFZFsZbNkycqzR-O!~%%~MTZ$+ozV*Z0|nr{BkDa+w?|YS^=n z?6yfC%CGC*YN$=~rNm;S7%4kIB`L)tv1qKFDz&%hqoGdxPKKRyH#hC(Zm9D8^IpYD z_iZsuCa!A5-(r?ql#wb5HGM_b$Z9t8aGg&#l8;N=bcx!pD72p<7?nob?Vg=-Vt?*BU2~4m`4VF&Qn+&p-OMNoQ`4%qS$%qR%@H=OhcT``!bdiW z%0I@nT;h|rozmBi)cG3K{N>!ToQ^As!kS~%cEcMN{4&y}b*6!R%*A6`>566?*n1;S zrhE1&Im)JGHLA@c^2k(x%0H^D@NU$U+}i?2%MF2Cql_pc*9nz|mfeAZ=Dhx2%HX4; zb$-AYvs2u1icSuSqG=7>+nG+dCymamWhbv5XC5abAC+WkjeRrgH1x4pIke5#EL^@n zC9|#$T`j64B{${zso%fN>2@e&tc;IpDCTh697?(sMSWT3UB2eFDPgQl>u&TghsYrZ zmdZbRc&607fg>tr8K?6%D&&1*mTwgD6os|B59@?AJ9>S*u6rMRLf)Z$w= z-(}r$W`a#~Fe-HuZgRk={G-yz{lt@URDyhyk*gz)+mbUW9jndd5Rp(1)c}XW9 zMPW>A9oezmuTj%=?TPAWBC{k?M-_!ynU!k`+#NJB$fgyg^KK1ML-vzOqo4Nku5(bS zcZW7jnrYLz8KaR?wV%*tC)qW-EzGGR#tG*s_P&BI8;* z_x+cJ+q8BRqDqNUQi)1K$sVG<75}mEyW7}sod+8s>O8lc=Wpo|4Tz(cit4dU~Lw@&Ny3KV-DBIu@xoF zwf1m4Zc%9GvGdq^n>Nr$&KigYa+y;}S`(3*IfT@Sad^^dy*uw?+%WCrUOTx=;uS>~ z;{FXt9e9^Ez%`v z7rx8IT`rwcQL?Am)0<&y%SN3%Fjy)!Fmm{{Osr*6Q&B?`IehRAzqwy36xim%Z&E+( zw*?2e=Rxj^dMi{aT|FT=V27@Ss{>aka|@*dhN4i@v;1%1-P6l=+B9!Us)mc<(n+YK z3geJeZJ9Kob`#gWyL7%qxBcMeRYb~`C?-+yyTXbwD~Ivtyi74 zbj)7+{vNB=m|~yR(<*~El|~;CyvwpJf9GxTD8_}~&d|qL?itJT!P1Y2y|H6D#>8gU zW*g4Cf|*y)*AO@Kb=o7*uv6xAEh#}N4Kn7v zm1reXd@4z6?tk-s+H1o2w${4wHm$m$*Hko>n+lbrR}L$|X?Ve&E6RoKm#L8Qwp8Yr zO7UA!bmCBQR=>G*wn(+z>TSlQoAkDBvV_LC>k(Uyoqd1ckJq9v-1E!tyTF`$f7z4& zTY6aMht&k#Vzyg!p`$1)wfgdTao3Y4j_Uk{afLLES*MXtCJnyC)XyG8I+>?@{uvk`X;#Wr$*O$JNK5s zj7n6s=zO*jixasik+Oe9p><%w-CETiw?3}(6^37>aZ?)km7>tfb>+_4 zcCWIZ(6uOan-b3~@s!jk3blwD+0wJU4LqUqooZS(aLWd|$5#|;OT>uAzfO9d%&Zls zLGU&E$_lB>(7%ntw@&b!vG$ZaIT}lLH503ubSy;;MJ(C#`-)60F|PD!o&Pkht>1F% zTiPcSMWrF%I}iA&JxQ`@RSnN-AzH{$rIL)5foGYk&9B>RlI@(XrKs&>C$sJ($5#~A z3YN8W8CEOmTxQLgx=__zRmNB3uiW&!Hf@}W zLT&!Eq&SaO3olr;X4F}mr%gs4Dyhn#|L!wW2l%XNnCa04y5Rfob@mfCefoXD7gja# z{(Z-Hvcf2}-aazRNAfjAQDNd}ZA8Pv*)Q3&CF)u+l8H!4l@$G>rv=;Bbv8xJyQFJ# zjZ5rv%zTcLRYhTO>94i@l5V!RY|{!;Agd{A%9M&q!gIqOf@0Jm^We z4~?$t+Fj$0`Vuo;A}>)CT6L>eS^X+JSZd8v``jYtw1~z{QK)%deSP%Xg(uf_KF#RF zMQ*uBM`lH#b$i8Ymz{azZs`1)8n`fS38Q^ZQK-2_bnHK)a{3LM=1-gEP%%{Q(^OJ7 zW3g#IKKXY)-@qI17>CT*V79Y9$^G8f5QYAR}I+OvdH z>7%Cos=Zk%)-&eJRk+GQrqa;DoK?(!b@I%x?NY5bC9f021Ua)*lGaxwubm3dE>UI6 zl-s(tNyU*k<`zf$fuiW{_nduq9xFWO4o!sdPPT-Zme5Qn3bo6NzWT+XNQn^_x9i|`IN-=Vozl$tk8?JzmA_-`6AH2R)Rt7F8%#-g#T zl1fslj9B=@C*E;+7a3#54;FUHI8Kqb}q8S`GQqT8Mt?_8eh{GFPqFU<0Va%)9l zY-#;lDaBK2zp!a-jnsNG6Pw9@6(x%ukXbLzf5lv2?wyxbtp!c0$8vSW!dM?&Y!fx8EL^&^48&!FZzB$4vWZ8WcqX2i!TQHQPJt zwM}bJ>u{79CB2+VvK51Mc)WMcdbjTNc$Y<+Ol=Ec4k46qNuxh>J&*`zV@mII0!aMbyL9-*x_4&DUpU`AoZwqOdn6?fvPk z`-l9nX--B5yoI-n4OITofyQZ5GCL&A`$6{#bZ!Y|mS7qXMPY1wn|2M#^pBHT`_;O- z!%TN*3KWIf`d*Kl_d8kWr-TY9wKd#z4V~TNZe4w>?2 zUu$0-*(gwQl&L9%9Dp4_#*svU;&40$7>*MStAn) zrG`p@Qlo8LfzyyJ0-Pq~H-t>fU_6L>2k~M6bI96)xlb@J0W6VZ%-}g^a6iB$%2qw@ zcx!A2H?x#DI6HXY0#p9X>YUXBP%`<`!Tqu8669T{{RvDm?lW

9`-3j9@N;xt?Lf=WsEH>$oeI8X;VSa2;ExWbEUG_VLm{Az;wSdkDOjVR}<|_7v`m z=L|sjsQHB3!?FNmkN66&2NwrUk0c7ChnrPk^GKrLc<@8O@kpW&c#r^rM-qj;gNFij zM-qj!g9J!B@@fUJ!}15fj=oWtI@AM99etyqbod5PI{JnHbT}7d^og(Fa=oqQcU??Q`# z?;?r9bRlQ}(?t>jav=Z#!$lFyfNuSE<&#{Td*#0O>px=AvExCoL*Mmva7GG=ozn_F-*A{h&~SimiKV~~t}yvjaa z8@-W?L%hNvULCi~lJT00*W7|P4#{}K#T#zH!@gv^<>IY;Ke69#Nya-a-f;_7hGe|w z;yt$@9!o|#7wO!BBNXszG-L{+hBX9?8c7r`4YQ8TnP!(jX&LsXHQaj*FM_NT$TTvc z&}eWeI0~6CFlfIWie;AT+?S;J0ML4m|s9B$A6r?uvvC^F&4z@C+UU z;2B92<_wqGfSHk(DRddy4s;oPqX1>F20$4}6ow3qM5C#(cHG$S#;)OF4R^ty0YpYd z2s?&c6Sy(r?>8W0Fb=2~NeCc@2n}o)@f92l4Fw#GBnknBD2SAf8cOi540D^v^+cWr zSH=K=kqL$Sf^p!!NJ3~Y%r$^s#8=oZd;x41NeIz}T@+w0;uC}me4w|8f5ZT8VSEAD zB8kFip$=fQNTP6Am}x+=NTP69h%~@pkwn3+V69W*grt2(mv*Iia*K&u%nVSAWZY)r zHZ$WXS~9*d@r{`QZ;*`dOnhf%n3s(8T&(A2oCPE!ii;?2#)(KWVtL6}UIEWC2BS(W zua$uj&&$N~${FHGJg*kd>t7B0D)xuV&WHbga=8+CN4H{N4zKk-bS?&stxb^K(rB`aBOG|(s$x3(As|v zWCyL0MB&u1%79ZN3Bl8_E&)v=zQUuyeSk$HiGrWO7Pci?Wp>Qj?{Kh~iN(wXi3&Du zQZ;~QzZDlTv547l0}B`$)uy0l2v~rgk%SOu*qwnkBR*lwzz4pJ_=GCM0>-{Wd<7!I zUI7ppNfdGnV*u0`NeC>4?f@r7;Z>o-u(Ys{=nDl1g9Lyuk|+=udI%60NeKIe`A5b~ ze1-Kwi-Gkb2_d}DM}WJCukc)`9e6I1C=eI=2mlvJ2(|@VKx`48fLa(|fV7COfLZ7V zfLS!z1|IA8WQQ^l%3SfRjN`47R3v0phA`dR+}jCvEpnkuXJR@t;W|Vzf|v+mri=De zNiudbv74FTtdg;Zi9O5&_@-pUFcHH{I6O#3EEBQJgj`ZGE;4aZ{*jjbufQbZ857T# z31F3=-V7(0m&xvETE3nrA=FeQ{X^#2A@SATgK#NQ@*37lwuc7e*3-gW(W3^YihP zR?R;J)sX|};_2dp4#=OrIehaVuF0Q%o_;bAC^BW{Hk`cVGt>)qEkgU|N!?w9drfA9Voe-6n$Bsao{ z{C1r8IA5$A`7@Y_U}nZ8t7ObzVg@tg)=x5KG51-_3$QcEn9ba0GcR14NX8uIK8JY$ zUm_WEnfqMkg;S+ugfS7u%t)goV+HeB!3rRykc^GYdn5D3MWAHFGZD|scqNyN{Y>m< zW~6nJk;p_MGh;#}<2Vz?nHedtWSnQ>JToIRmy8QcTwrEAPDsWLCT=h@E^{Q~ArlXo z8E_uSc*MjbX2xZUWV~SFh5W=0caw~lOuS@fxSV99Fph)c!_E>>_e&i0ZK!9@f&W3fucYA#lDGaf}HV*?i( zxEVMF$=J%pR&K^ATr#4$Yc$V+!?$GY<~eusyx7wuBbNKda=$;4vx4!w_#e4g{)4<| zhSV#8iv(`QkxAM(#l*o8@vc^CnVzz_qxOLVJnl2mt4H$W?+dVBbAF(ZpK}oWW3_y6*uD|O)@@n z@6X)#kK3qxUw9GRMgeN2spSerszjV(+r!~3GRM^7ePvcyJET$yRDFOmX6bKbr8qU4cPCMfCcLUxq z@rF6!Oo*hPj3~${%p+2g5d&%Z+r+<2{L7qhmxP5#CKS{ZCa~T}qR^#~;O>GX3Q&qn z6M#~Z5R4Sj9d{YTm-qAX{us7^8YKyVMG@1nFp00Qq4)yWP?8WN6kP=zl=y@K#omF# z1MwB=6ZsENpCloyC*nG;K!~rPoyg+=?IejpbE4zoA5^BMmF016*ftkN2FP57nH>v)4_!?Fbylp0D1Q0%jKBq6>6e4^=i{~!s$JdrlJ4vFz zoY>27=rSY*f)n2Wxk(bjYocA)Nr|uEnOItYXOcu=G7*V@$0UiuU}AH{NYc?kfi6)$ zvSa#2;Vf~j0h}dC2wsW&0ecDY6_^sX5CBP{0F-b!JY>;IHgJ=_onbN0wU`&hB|n^k z8mf?!FpjQ}2|HTyw~@U}>}Bq_O9FI~+Chj(EJvUviBA|wj5W}a#3xK7_(-&guMm)E z3=ojU)0xCQ!Z)NJ_9)P!AY4cO>~HP=-_sd_J9SQhAu&DxLsCr&|A?A^e*Ji-sK z)M!8qsN?V9IL5^>t^);y9Fj~Ja7Vi$vPa?*!V#Uobrtaysu8gUs78_~BqKV29E~Ij zyoi{Bq<|y}uZXT8u9C!nDcaiwj3V(BE)jE&OIwmC7$WKi43Q)RKExCPcSw8%HAK7M z6eKYahW6TFk@$8@p z!Wv$DY03={puJ_%&my@`Brk^BTi^l7(-aI4-9o-e#uf4p#)14Ji30nfskmh#iNf|_ zdk40UBn0WhpyIqqe1+gcEkN**gm8P&c2Vi<6QP4cJ3kfJm6c7)!0EkBt z1-?UzarHwIg||bzLy|}mg|R~mkWQ0C!Rk;CV09!RG#z3jfON!H*g3QrusM<_tQ?{@ zaB?IebR0$oz&PR)9u6Bk&=AB|=r_1F&~GGBfH(9JM+cH9j2n7_R*-Aifo;E?>mwH* zxeMMs0COWF1a8Ax!{a>h6_gFe0c9hJLa(7gK(CQR0oHI7$7>5o6i5v&43HX02%3ia zaRp0!g+#*y;;=^&g+7Bj0DVRh0-a%Sap)qxf|#K%j@#dz|7*q1=MHkw1&TmRhFIS_ zd+$65s!}?QiD}G&04^CjnAjm|_?iD9JdUq}UA<&{V&aqh8!9`< z2=F#)m4dV(!2rsHrjMX(_PHbM%mb!BU^(zu3_u&1P$)LcJP>RoQJ^*W6F_SuA*>p_ z9Fs_u+OcWBeP%Hii@6K-b#6zhkqN@5!MTyIQJ_+YG|U;!C-j8^p}{i%LL-Sno}mjk zJCj5K%`j^KnvsOyWvCyQ9>iB@GHkCvlaWLL$Y2YvP9#y7F-Y)0N)iPV!w_NyNurQp z_-0tIf!%4;h7ap8ygdEKJqhl!|Ksw(z2Tiv-itj>X1dSr$*I)j|Ni@T|M!3YANFZy A=l}o! literal 20476 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 8) & 0xFF; buf[pos++] = sb.knxAddrWrite & 0xFF; buf[pos++] = (sb.knxAddrWrite >> 8) & 0xFF; - buf[pos++] = sb.colorOn.r; - buf[pos++] = sb.colorOn.g; - buf[pos++] = sb.colorOn.b; - buf[pos++] = sb.colorOff.r; - buf[pos++] = sb.colorOff.g; - buf[pos++] = sb.colorOff.b; + buf[pos++] = sb.bgColorOn.r; + buf[pos++] = sb.bgColorOn.g; + buf[pos++] = sb.bgColorOn.b; + buf[pos++] = sb.bgColorOff.r; + buf[pos++] = sb.bgColorOff.g; + buf[pos++] = sb.bgColorOff.b; + buf[pos++] = sb.iconColorOn.r; + buf[pos++] = sb.iconColorOn.g; + buf[pos++] = sb.iconColorOn.b; + buf[pos++] = sb.iconColorOff.r; + buf[pos++] = sb.iconColorOff.g; + buf[pos++] = sb.iconColorOff.b; buf[pos++] = static_cast(sb.position); buf[pos++] = static_cast(sb.action); buf[pos++] = sb.targetScreen; @@ -307,7 +313,7 @@ void WidgetConfig::deserialize(const uint8_t* buf) { for (size_t i = 0; i < MAX_SUBBUTTONS; ++i) { SubButtonConfig& sb = subButtons[i]; - if (pos + 24 <= SERIALIZED_SIZE) { + if (pos + 30 <= SERIALIZED_SIZE) { sb.iconCodepointOff = buf[pos] | (buf[pos + 1] << 8) | (buf[pos + 2] << 16) | (buf[pos + 3] << 24); pos += 4; @@ -316,12 +322,18 @@ void WidgetConfig::deserialize(const uint8_t* buf) { pos += 4; sb.knxAddrRead = buf[pos] | (buf[pos + 1] << 8); pos += 2; sb.knxAddrWrite = buf[pos] | (buf[pos + 1] << 8); pos += 2; - sb.colorOn.r = buf[pos++]; - sb.colorOn.g = buf[pos++]; - sb.colorOn.b = buf[pos++]; - sb.colorOff.r = buf[pos++]; - sb.colorOff.g = buf[pos++]; - sb.colorOff.b = buf[pos++]; + sb.bgColorOn.r = buf[pos++]; + sb.bgColorOn.g = buf[pos++]; + sb.bgColorOn.b = buf[pos++]; + sb.bgColorOff.r = buf[pos++]; + sb.bgColorOff.g = buf[pos++]; + sb.bgColorOff.b = buf[pos++]; + sb.iconColorOn.r = buf[pos++]; + sb.iconColorOn.g = buf[pos++]; + sb.iconColorOn.b = buf[pos++]; + sb.iconColorOff.r = buf[pos++]; + sb.iconColorOff.g = buf[pos++]; + sb.iconColorOff.b = buf[pos++]; sb.position = static_cast(buf[pos++]); sb.action = static_cast(buf[pos++]); sb.targetScreen = buf[pos++]; @@ -566,6 +578,8 @@ void GuiConfig::clear() { knxDateAddress = 0; knxDateTimeAddress = 0; knxNightModeAddress = 0; + screenAnimType = ScreenAnimType::FADE; + screenAnimDuration = 300; // Default 300ms for (size_t i = 0; i < MAX_SCREENS; i++) { screens[i].clear(static_cast(i), nullptr); } diff --git a/main/WidgetConfig.hpp b/main/WidgetConfig.hpp index de52883..d232e7e 100644 --- a/main/WidgetConfig.hpp +++ b/main/WidgetConfig.hpp @@ -169,20 +169,22 @@ enum class SubButtonAction : uint8_t { NAVIGATE = 1, // Navigate to screen }; -// Sub-button configuration for RoomCard (24 bytes) +// Sub-button configuration for RoomCard (30 bytes) struct SubButtonConfig { uint32_t iconCodepointOff; // 4 bytes - Icon codepoint when OFF uint32_t iconCodepointOn; // 4 bytes - Icon codepoint when ON (0 = use iconCodepointOff) uint16_t knxAddrRead; // 2 bytes - KNX address to read status uint16_t knxAddrWrite; // 2 bytes - KNX address to write on click - Color colorOn; // 3 bytes - Color when ON - Color colorOff; // 3 bytes - Color when OFF + Color bgColorOn; // 3 bytes - Background color when ON + Color bgColorOff; // 3 bytes - Background color when OFF + Color iconColorOn; // 3 bytes - Icon color when ON + Color iconColorOff; // 3 bytes - Icon color when OFF SubButtonPosition position; // 1 byte - Position around bubble SubButtonAction action; // 1 byte - Action type uint8_t targetScreen; // 1 byte - Target screen for navigate bool enabled; // 1 byte - Is this sub-button active? uint8_t _padding[2]; // 2 bytes - Alignment padding - // Total: 24 bytes per SubButton + // Total: 30 bytes per SubButton }; // Text line configuration for RoomCard (24 bytes) @@ -297,8 +299,8 @@ struct WidgetConfig { uint8_t arcValueFontSize; // Center value font size index // Serialization size (fixed for NVS storage) - // 345 + 24 (6 subbuttons * 4 bytes for iconCodepointOn) = 369 - static constexpr size_t SERIALIZED_SIZE = 369; + // 369 + 36 (6 subbuttons * 6 bytes for icon colors) = 405 + static constexpr size_t SERIALIZED_SIZE = 405; void serialize(uint8_t* buf) const; void deserialize(const uint8_t* buf); @@ -341,6 +343,20 @@ struct ScreenConfig { const WidgetConfig* findWidget(uint8_t id) const; }; +// Screen transition animation types +enum class ScreenAnimType : uint8_t { + NONE = 0, + FADE = 1, + SLIDE_LEFT = 2, + SLIDE_RIGHT = 3, + SLIDE_UP = 4, + SLIDE_DOWN = 5, + OVER_LEFT = 6, + OVER_RIGHT = 7, + OVER_UP = 8, + OVER_DOWN = 9, +}; + struct GuiConfig { uint8_t screenCount; ScreenConfig screens[MAX_SCREENS]; @@ -353,6 +369,10 @@ struct GuiConfig { uint16_t knxDateTimeAddress; uint16_t knxNightModeAddress; + // Screen transition animation + ScreenAnimType screenAnimType; + uint16_t screenAnimDuration; // Duration in ms (0 = default 300ms) + void clear(); ScreenConfig* findScreen(uint8_t id); const ScreenConfig* findScreen(uint8_t id) const; diff --git a/main/WidgetManager.cpp b/main/WidgetManager.cpp index e9890c0..36812f3 100644 --- a/main/WidgetManager.cpp +++ b/main/WidgetManager.cpp @@ -359,6 +359,23 @@ void WidgetManager::applyScreen(uint8_t screenId) { esp_lv_adapter_unlock(); } +// Helper to convert ScreenAnimType to LVGL animation type (LVGL 9.x API) +static lv_screen_load_anim_t screenAnimToLvgl(ScreenAnimType type) { + switch (type) { + case ScreenAnimType::NONE: return LV_SCREEN_LOAD_ANIM_NONE; + case ScreenAnimType::FADE: return LV_SCREEN_LOAD_ANIM_FADE_IN; + case ScreenAnimType::SLIDE_LEFT: return LV_SCREEN_LOAD_ANIM_MOVE_LEFT; + case ScreenAnimType::SLIDE_RIGHT: return LV_SCREEN_LOAD_ANIM_MOVE_RIGHT; + case ScreenAnimType::SLIDE_UP: return LV_SCREEN_LOAD_ANIM_MOVE_TOP; + case ScreenAnimType::SLIDE_DOWN: return LV_SCREEN_LOAD_ANIM_MOVE_BOTTOM; + case ScreenAnimType::OVER_LEFT: return LV_SCREEN_LOAD_ANIM_OVER_LEFT; + case ScreenAnimType::OVER_RIGHT: return LV_SCREEN_LOAD_ANIM_OVER_RIGHT; + case ScreenAnimType::OVER_UP: return LV_SCREEN_LOAD_ANIM_OVER_TOP; + case ScreenAnimType::OVER_DOWN: return LV_SCREEN_LOAD_ANIM_OVER_BOTTOM; + default: return LV_SCREEN_LOAD_ANIM_FADE_IN; + } +} + void WidgetManager::applyScreenLocked(uint8_t screenId) { ESP_LOGI(TAG, "applyScreen(%d) - start", screenId); @@ -375,35 +392,40 @@ void WidgetManager::applyScreenLocked(uint8_t screenId) { closeModalLocked(); } - lv_display_t* disp = lv_display_get_default(); - if (disp) { - lv_display_enable_invalidation(disp, false); - } - - // SAFE DESTRUCTION: + // SAFE DESTRUCTION of C++ widgets: // 1. Mark all C++ widgets as "LVGL object already gone" for (auto& widget : widgets_) { if (widget) widget->clearLvglObject(); } - - // 2. Delete all LVGL objects on layers we use - lv_obj_clean(lv_scr_act()); + // 2. Clean layer_top (sub-buttons etc) lv_obj_clean(lv_layer_top()); - - // 3. Now destroy C++ objects (their destructors won't call lv_obj_delete) + // 3. Destroy C++ objects (their destructors won't call lv_obj_delete) destroyAllWidgets(); + // Create a NEW screen object for the transition + lv_obj_t* newScreen = lv_obj_create(NULL); + lv_obj_remove_style_all(newScreen); + lv_obj_set_size(newScreen, LV_PCT(100), LV_PCT(100)); + lv_obj_clear_flag(newScreen, LV_OBJ_FLAG_SCROLLABLE); + ESP_LOGI(TAG, "Creating new widgets for screen '%s' (%d widgets)...", screen->name, screen->widgetCount); - lv_obj_t* root = lv_scr_act(); - createAllWidgets(*screen, root); + createAllWidgets(*screen, newScreen); ESP_LOGI(TAG, "Widgets created"); applyCachedValuesToWidgets(); - if (disp) { - lv_display_enable_invalidation(disp, true); - } - lv_obj_invalidate(lv_scr_act()); + // Get animation settings + ESP_LOGI(TAG, "Animation config: type=%d, duration=%u", + (int)config_->screenAnimType, config_->screenAnimDuration); + lv_screen_load_anim_t animType = screenAnimToLvgl(config_->screenAnimType); + uint32_t animDuration = config_->screenAnimDuration > 0 ? config_->screenAnimDuration : 300; + + ESP_LOGI(TAG, "Loading screen with animation: lvgl_type=%d, duration=%lums", + (int)animType, (unsigned long)animDuration); + + // Load the new screen with animation (old screen auto-deleted after animation) + // Parameters: new_scr, anim_type, time_ms, delay_ms, auto_del_old_scr + lv_screen_load_anim(newScreen, animType, animDuration, 0, true); ESP_LOGI(TAG, "applyScreen(%d) - complete", screenId); } @@ -1503,6 +1525,11 @@ void WidgetManager::getConfigJson(char* buf, size_t bufSize) const { cJSON_AddNumberToObject(knx, "dateTime", config_->knxDateTimeAddress); cJSON_AddNumberToObject(knx, "night", config_->knxNightModeAddress); + // Screen transition animation + cJSON* anim = cJSON_AddObjectToObject(root, "screenAnim"); + cJSON_AddNumberToObject(anim, "type", static_cast(config_->screenAnimType)); + cJSON_AddNumberToObject(anim, "duration", config_->screenAnimDuration); + cJSON* screens = cJSON_AddArrayToObject(root, "screens"); for (uint8_t s = 0; s < config_->screenCount; s++) { @@ -1722,13 +1749,19 @@ void WidgetManager::getConfigJson(char* buf, size_t bufSize) const { cJSON_AddNumberToObject(sbJson, "action", static_cast(sb.action)); cJSON_AddNumberToObject(sbJson, "target", sb.targetScreen); - char colorOnStr[8], colorOffStr[8]; - snprintf(colorOnStr, sizeof(colorOnStr), "#%02X%02X%02X", - sb.colorOn.r, sb.colorOn.g, sb.colorOn.b); - snprintf(colorOffStr, sizeof(colorOffStr), "#%02X%02X%02X", - sb.colorOff.r, sb.colorOff.g, sb.colorOff.b); - cJSON_AddStringToObject(sbJson, "colorOn", colorOnStr); - cJSON_AddStringToObject(sbJson, "colorOff", colorOffStr); + char bgColorOnStr[8], bgColorOffStr[8], iconColorOnStr[8], iconColorOffStr[8]; + snprintf(bgColorOnStr, sizeof(bgColorOnStr), "#%02X%02X%02X", + sb.bgColorOn.r, sb.bgColorOn.g, sb.bgColorOn.b); + snprintf(bgColorOffStr, sizeof(bgColorOffStr), "#%02X%02X%02X", + sb.bgColorOff.r, sb.bgColorOff.g, sb.bgColorOff.b); + snprintf(iconColorOnStr, sizeof(iconColorOnStr), "#%02X%02X%02X", + sb.iconColorOn.r, sb.iconColorOn.g, sb.iconColorOn.b); + snprintf(iconColorOffStr, sizeof(iconColorOffStr), "#%02X%02X%02X", + sb.iconColorOff.r, sb.iconColorOff.g, sb.iconColorOff.b); + cJSON_AddStringToObject(sbJson, "bgColorOn", bgColorOnStr); + cJSON_AddStringToObject(sbJson, "bgColorOff", bgColorOffStr); + cJSON_AddStringToObject(sbJson, "iconColorOn", iconColorOnStr); + cJSON_AddStringToObject(sbJson, "iconColorOff", iconColorOffStr); cJSON_AddItemToArray(subButtons, sbJson); } @@ -2199,14 +2232,42 @@ bool WidgetManager::updateConfigFromJson(const char* json) { sb.targetScreen = target->valueint; } - cJSON* colorOn = cJSON_GetObjectItem(sbItem, "colorOn"); - if (cJSON_IsString(colorOn)) { - sb.colorOn = Color::fromHex(parseHexColor(colorOn->valuestring)); + // Background colors + cJSON* bgColorOn = cJSON_GetObjectItem(sbItem, "bgColorOn"); + if (cJSON_IsString(bgColorOn)) { + sb.bgColorOn = Color::fromHex(parseHexColor(bgColorOn->valuestring)); + } else { + // Backward compatibility: try old "colorOn" field + cJSON* colorOn = cJSON_GetObjectItem(sbItem, "colorOn"); + if (cJSON_IsString(colorOn)) { + sb.bgColorOn = Color::fromHex(parseHexColor(colorOn->valuestring)); + } } - cJSON* colorOff = cJSON_GetObjectItem(sbItem, "colorOff"); - if (cJSON_IsString(colorOff)) { - sb.colorOff = Color::fromHex(parseHexColor(colorOff->valuestring)); + cJSON* bgColorOff = cJSON_GetObjectItem(sbItem, "bgColorOff"); + if (cJSON_IsString(bgColorOff)) { + sb.bgColorOff = Color::fromHex(parseHexColor(bgColorOff->valuestring)); + } else { + // Backward compatibility: try old "colorOff" field + cJSON* colorOff = cJSON_GetObjectItem(sbItem, "colorOff"); + if (cJSON_IsString(colorOff)) { + sb.bgColorOff = Color::fromHex(parseHexColor(colorOff->valuestring)); + } + } + + // Icon colors (default to white if not specified) + cJSON* iconColorOn = cJSON_GetObjectItem(sbItem, "iconColorOn"); + if (cJSON_IsString(iconColorOn)) { + sb.iconColorOn = Color::fromHex(parseHexColor(iconColorOn->valuestring)); + } else { + sb.iconColorOn = {255, 255, 255}; // Default white + } + + cJSON* iconColorOff = cJSON_GetObjectItem(sbItem, "iconColorOff"); + if (cJSON_IsString(iconColorOff)) { + sb.iconColorOff = Color::fromHex(parseHexColor(iconColorOff->valuestring)); + } else { + sb.iconColorOff = {255, 255, 255}; // Default white } sbIdx++; @@ -2410,6 +2471,19 @@ bool WidgetManager::updateConfigFromJson(const char* json) { if (cJSON_IsNumber(nightAddr)) newConfig->knxNightModeAddress = nightAddr->valueint; } + // Screen transition animation + cJSON* anim = cJSON_GetObjectItem(root, "screenAnim"); + if (cJSON_IsObject(anim)) { + cJSON* animType = cJSON_GetObjectItem(anim, "type"); + if (cJSON_IsNumber(animType)) { + newConfig->screenAnimType = static_cast(animType->valueint); + } + cJSON* animDuration = cJSON_GetObjectItem(anim, "duration"); + if (cJSON_IsNumber(animDuration)) { + newConfig->screenAnimDuration = static_cast(animDuration->valueint); + } + } + if (newConfig->screenCount == 0) { cJSON_Delete(root); return false; diff --git a/main/widgets/RoomCardWidgetBase.cpp b/main/widgets/RoomCardWidgetBase.cpp index 3491047..6d1d93a 100644 --- a/main/widgets/RoomCardWidgetBase.cpp +++ b/main/widgets/RoomCardWidgetBase.cpp @@ -150,7 +150,10 @@ void RoomCardWidgetBase::applySubButtonStyle() { for (uint8_t i = 0; i < config_.subButtonCount && i < MAX_SUBBUTTONS; ++i) { if (subButtonIcons_[i] && subBtnIconFont) { lv_obj_set_style_text_font(subButtonIcons_[i], subBtnIconFont, 0); - lv_obj_set_style_text_color(subButtonIcons_[i], lv_color_white(), 0); + // Set initial icon color based on state (OFF by default) + const Color& iconColor = subButtonStates_[i] ? + config_.subButtons[i].iconColorOn : config_.subButtons[i].iconColorOff; + lv_obj_set_style_text_color(subButtonIcons_[i], lv_color_hex(iconColor.toLvColor()), 0); } } } @@ -160,11 +163,12 @@ void RoomCardWidgetBase::updateSubButtonColor(uint8_t index) { const SubButtonConfig& cfg = config_.subButtons[index]; bool isOn = subButtonStates_[index]; - const Color& color = isOn ? cfg.colorOn : cfg.colorOff; - lv_obj_set_style_bg_color(subButtonObjs_[index], lv_color_hex(color.toLvColor()), 0); + // Update background color + const Color& bgColor = isOn ? cfg.bgColorOn : cfg.bgColorOff; + lv_obj_set_style_bg_color(subButtonObjs_[index], lv_color_hex(bgColor.toLvColor()), 0); - // Update icon based on state + // Update icon and icon color based on state if (subButtonIcons_[index]) { uint32_t iconCodepoint = isOn && cfg.iconCodepointOn > 0 ? cfg.iconCodepointOn : cfg.iconCodepointOff; if (iconCodepoint > 0) { @@ -172,6 +176,10 @@ void RoomCardWidgetBase::updateSubButtonColor(uint8_t index) { encodeUtf8(iconCodepoint, iconText); lv_label_set_text(subButtonIcons_[index], iconText); } + + // Set icon color + const Color& iconColor = isOn ? cfg.iconColorOn : cfg.iconColorOff; + lv_obj_set_style_text_color(subButtonIcons_[index], lv_color_hex(iconColor.toLvColor()), 0); } } diff --git a/sdkconfig b/sdkconfig index 5ccc069..8e04bc6 100644 --- a/sdkconfig +++ b/sdkconfig @@ -2861,7 +2861,7 @@ CONFIG_LV_USE_BUILTIN_SPRINTF=y # # HAL Settings # -CONFIG_LV_DEF_REFR_PERIOD=33 +CONFIG_LV_DEF_REFR_PERIOD=16 CONFIG_LV_DPI_DEF=130 # end of HAL Settings @@ -2933,12 +2933,12 @@ CONFIG_LV_USE_DRAW_SW_ASM=0 # CONFIG_LV_USE_LOG=y # CONFIG_LV_LOG_LEVEL_TRACE is not set -CONFIG_LV_LOG_LEVEL_INFO=y +# CONFIG_LV_LOG_LEVEL_INFO is not set # CONFIG_LV_LOG_LEVEL_WARN is not set -# CONFIG_LV_LOG_LEVEL_ERROR is not set +CONFIG_LV_LOG_LEVEL_ERROR=y # CONFIG_LV_LOG_LEVEL_USER is not set # CONFIG_LV_LOG_LEVEL_NONE is not set -CONFIG_LV_LOG_LEVEL=1 +CONFIG_LV_LOG_LEVEL=3 CONFIG_LV_LOG_PRINTF=y CONFIG_LV_LOG_USE_TIMESTAMP=y CONFIG_LV_LOG_USE_FILE_LINE=y diff --git a/web-interface/src/components/SettingsModal.vue b/web-interface/src/components/SettingsModal.vue index 083aec4..00c7907 100644 --- a/web-interface/src/components/SettingsModal.vue +++ b/web-interface/src/components/SettingsModal.vue @@ -30,6 +30,28 @@ +

+
Screen-Animation
+
+ + +
+
+ + +
+
KNX Zeit
diff --git a/web-interface/src/components/widgets/elements/RoomCardElement.vue b/web-interface/src/components/widgets/elements/RoomCardElement.vue index 345c948..aae0571 100644 --- a/web-interface/src/components/widgets/elements/RoomCardElement.vue +++ b/web-interface/src/components/widgets/elements/RoomCardElement.vue @@ -24,8 +24,8 @@ class="absolute rounded-full flex items-center justify-center shadow-md" :style="getSubButtonStyle(sb, idx)" > - - {{ String.fromCodePoint(sb.icon) }} + + {{ String.fromCodePoint(getSubButtonIcon(sb)) }}
@@ -56,8 +56,8 @@ class="absolute rounded-full flex items-center justify-center shadow-md" :style="getSubButtonStyleTile(sb, idx)" > - - {{ String.fromCodePoint(sb.icon) }} + + {{ String.fromCodePoint(getSubButtonIcon(sb)) }}
@@ -162,13 +162,15 @@ const getSubButtonStyleTile = (sb, idx) => { const btnSize = (props.widget.subButtonSize || 40) * s; const gap = 10 * s; const padding = 12 * s; + // Use bgColorOff with fallback to old colorOff for compatibility + const bgColor = sb.bgColorOff || sb.colorOff || '#666666'; return { width: btnSize + 'px', height: btnSize + 'px', right: padding + 'px', top: (padding + idx * (btnSize + gap)) + 'px', - backgroundColor: sb.colorOff || '#666666', + backgroundColor: bgColor, }; }; @@ -241,7 +243,8 @@ function getSubButtonStyle(sb, idx) { const angle = (pos * (Math.PI / 4)) - (Math.PI / 2); const x = centerX + orbitRadius * Math.cos(angle) - subBtnSize / 2; const y = centerY + orbitRadius * Math.sin(angle) - subBtnSize / 2; - const bgColor = sb.colorOff || '#666666'; + // Use bgColorOff with fallback to old colorOff for compatibility + const bgColor = sb.bgColorOff || sb.colorOff || '#666666'; return { width: `${subBtnSize}px`, @@ -257,12 +260,19 @@ function getSubButtonIconStyle(sb) { const s = props.scale; const subBtnSize = (props.widget.subButtonSize || 40) * s; const iconSize = subBtnSize * 0.5; + // Use iconColorOff with fallback for compatibility + const iconColor = sb.iconColorOff || '#ffffff'; return { fontSize: `${iconSize}px`, - color: '#ffffff' + color: iconColor }; } +function getSubButtonIcon(sb) { + // Use iconOff with fallback to old icon field for compatibility + return sb.iconOff || sb.icon || 0; +} + const computedStyle = computed(() => { const w = props.widget; const s = props.scale; diff --git a/web-interface/src/components/widgets/settings/RoomCardSettings.vue b/web-interface/src/components/widgets/settings/RoomCardSettings.vue index 129049e..ca94ddd 100644 --- a/web-interface/src/components/widgets/settings/RoomCardSettings.vue +++ b/web-interface/src/components/widgets/settings/RoomCardSettings.vue @@ -212,12 +212,19 @@ -
- +
+ An: - + Aus: - + +
+
+ + An: + + Aus: +
@@ -331,8 +338,10 @@ const subButtonCount = computed({ knxWrite: 0, action: 0, target: 0, - colorOn: '#FFCC00', - colorOff: '#666666' + bgColorOn: '#FFCC00', + bgColorOff: '#666666', + iconColorOn: '#FFFFFF', + iconColorOff: '#FFFFFF' }); } if (props.widget.subButtons.length > target) { diff --git a/web-interface/src/stores/editor.js b/web-interface/src/stores/editor.js index 1f515a1..26e83de 100644 --- a/web-interface/src/stores/editor.js +++ b/web-interface/src/stores/editor.js @@ -8,6 +8,7 @@ export const useEditorStore = defineStore('editor', () => { startScreen: 0, standby: { enabled: false, screen: -1, minutes: 5 }, knx: { time: 0, date: 0, dateTime: 0, night: 0 }, + screenAnim: { type: 1, duration: 300 }, // 1 = Fade, 300ms screens: [] }); @@ -209,6 +210,12 @@ export const useEditorStore = defineStore('editor', () => { if (config.knx.dateTime === undefined) config.knx.dateTime = 0; if (config.knx.night === undefined) config.knx.night = 0; } + if (!config.screenAnim) { + config.screenAnim = { type: 1, duration: 300 }; + } else { + if (config.screenAnim.type === undefined) config.screenAnim.type = 1; + if (config.screenAnim.duration === undefined) config.screenAnim.duration = 300; + } mapLegacyKnxAddresses(); // Recalculate IDs