From 5895ccc29a09686060db0126e91d429fd9d41c6e Mon Sep 17 00:00:00 2001 From: Danil Iarosh Date: Thu, 21 May 2026 09:30:15 +0300 Subject: [PATCH] Release icon and connection direction update --- LEDMapper.spec | 19 ++++++++++++++++--- assets/ledmapper.ico | Bin 0 -> 270398 bytes canvas.py | 36 ++++++++++++++++++++++++++++++++++-- icons.py | 1 + main.py | 15 ++++++++++++++- qa_smoke.py | 29 +++++++++++++++++++++++++++++ requirements.txt | 3 ++- 7 files changed, 96 insertions(+), 7 deletions(-) create mode 100644 assets/ledmapper.ico diff --git a/LEDMapper.spec b/LEDMapper.spec index c882afb..ad89e27 100644 --- a/LEDMapper.spec +++ b/LEDMapper.spec @@ -1,12 +1,24 @@ # -*- mode: python ; coding: utf-8 -*- +from pathlib import Path + + +spec_dir = Path(SPECPATH) + a = Analysis( - ['main.py'], - pathex=['.'], + [str(spec_dir / 'main.py')], + pathex=[str(spec_dir), str(spec_dir.parent)], binaries=[], datas=[], - hiddenimports=[], + hiddenimports=[ + 'canvas', + 'icons', + 'main_window', + 'models', + 'panel_mapping_generator', + 'unfolding', + ], hookspath=[], hooksconfig={}, runtime_hooks=[], @@ -35,4 +47,5 @@ exe = EXE( target_arch=None, codesign_identity=None, entitlements_file=None, + icon=str(spec_dir / 'assets' / 'ledmapper.ico'), ) diff --git a/assets/ledmapper.ico b/assets/ledmapper.ico new file mode 100644 index 0000000000000000000000000000000000000000..37acc48af341a48ad8f3815cf5f61da4ce6302e5 GIT binary patch literal 270398 zcmeI5d5~4by~ppE#8l-UXGUOV2rdaQ1EYWe1&kFTR2-kd?YV(ZjS@h?BxqE`g?dmG zvOplwQhZ}2BZ><|Q3;eFi5BxT@_>jeN>J7j6r?Dv_m5RnX}x~^y>ky|P}!@ru`nYjUHem&Pqejm_%A(uO;`#OH+;V&CBgOdFz}HDV?$)eJ`V{(AS8ZT77?D1pdq6KG&Y-zh?GV`?`VVr? za+Ed@!3HENV8(U)hcz{QOXl}H`S&IBJwm>-+;+9x)*&~TnFoek^aIU}r=BP0MeHQ^ z-5g;3S#f_sYL7nD15OJL>{E%2Zdw1WWfyA7J%7qVLw#og(F2LGH;t zZ9t0+tN>#+)&=_YI;F47^Bv50E&1+hxkb6v*W-48HJf}u|AtdXNbZl2eX>s*Fk%Bo zz?=;}U|_HMi)5aE&U`nL?<`N%m#41k^mDM6qz`Crth+>V&o#HSNmm(iUGAe`FbN+} zDEG-e*>{~G*X2KIx*kwy@3YQN_FZQuE%JYEsz0F6&(FR-vY!@q(vprNy2k|yciy=Jj!I57|!}GigiD3fUj1e4e0WA9H%0)YXWAeZ{!3ZQ|~!6UE$F z4~q7O=8I=K+QsvW7v=x(9oKLV&)`|S<7#>-x0`mVFyAm&SlPe)S{{ey^N$%mMBG1Z zYM$-wTi+2!4t*T!5AVW9AS{HbFn<`9qh*OBHt>b2HADF|ybi461D|teKPdjW?#)=N zNAVE|8*$JVWWId0-+xPHRjeJB{toN+txRw0w|j_NZ@5mZS@E(q=KcHv;zZn#!B+iH zE<5ekV7|<&rV)_ex5vW0JI*?-r?}_#Nn*>!^~Sj`^#_mvvOp$-`O3H~Et4#>fiJ7r zN2tcOJ-Y@p)#uCH&Fj}C&V8vLfJ~?lKt{`KfbwM7SIE7@WF4U5{R9hpby{OPZ6k*! zQqM2tJnT2x1(20KAoF!^Yiyvfr)cD+0QMo4_G`sHy{&zEia9f9h=T{ZtSj^Vd;w&I z%#fXafPTON8`xdR-s1AyvdI?6EJhC-B-XyV${FrU{Q+c08f?%dWyrQ4l68PdQ7QnV z&X^akv6x-}^+G*%mNsYo=}6()d0F>!m)Re`DQP)AFJ}EY0nG zk9EIKq@8_&^Z};11M8*yw=~vvn3f}r{rrBc`(1|)OzWNTW!>l0c9ifh_x7njY|<_; zCho^X)~<7YK-PIQo&Ng6lK*3S1exyl$DFt{%9;sNts8=$4BZn>$ru`Z_6_W8See=_LCR6a1CjQg5~ zx|a+5*GOA9WWpY>E;h+^e4l)@Uu=7OgIK%z6|v%lrDEB$Pv-yd9oKLV&m`rKpc}3o z&N!Fm#@`Qt|A9SvG?`!@<6Nfq={m4?w^;Vflj6Q#Oc9q}c)mFMwD5U%cm~hn9lVP$ zO!@=ph+~5E0VbFP`vZE{x0#3wd*hhm=*`#Hh&v|UEC!$1RCRqCVIVAoX-aRR6Hb>-zTQrG|o6Hh!b(^ z@&nKr_ZH7MpSt@3CHuP9`|E7?gTEWEyu>*Bp1(xgI(-290m#2+YdSs)?C;ePwio8# zRxa08MkZ12d+G8%A%m_u0LB8mc=$nu); z!`WuLkV%KHgAQ#=E9J?&-w3ryU3GuV!?AIG9AwfpM`+4V#)%8hY2_J%y8FW!ekj*b zegFN_rfMk=Bljj#NQqBVA;;Wd`tIIwho9Xoeo^RN{XP0<=^-8gD!EeRSCfp!K z4L#e+dXg^PGu-5vFV2JQTi;R6Ki15BBCsn(e6Zzqk2q<^C@Y*Kyu~ zo_{m*K5@a|W@q#rHDCB#b>(_g{k1Leaog)@$dN_%x~1Szf#h9#`x8>4ak2w`S<+5Tb-|o$>XnKUm@!QWy@sN|I=0f z_qZ?p-0()Hxb4R4=m+Qrj`IWT|8u|puaxzm-y7e0Bfk!Tb!c^UXqR=#@jrLR|AIIV zyZ6#_Pv_Sux@^0Yy?P(U`9H~>Ar!`_?)x9@+aqR8zmsc~Gu$TN{NL{L{B*NsbNJ{R zxccJJ$F&PtwpL%Zx}Za@{~`aLZ~F72e-!8TKZAZCWAy>9|D_M`e88@My_-Kn&BlG3 zTqsXnlu^eV0Pg?cwok#{2*diH#=1ZT>H^&V%WYP@vRm~h&NaGh)c5HFQtkt||0ntP z^Bp)d>E55+%Jl>(_W|7hyLun6Y5iN`N5jt%*I#zAxO4I?0_Oo>-;Ot4Ta(ycgMM6B zdL4U{eXnmH)&)}P2YCKR_525&|A9S!e|YX-aqIQhiUo6KiB&Hx6WGh6QVx@R9($7g z_`(tN0V(tWy4nD+SDyb>ek>Vh2BUpF^X_S4`O;?v_61Io?9AiFV`5yN^`jV442OM!C?znP?y$7A?EDMKx zr+5CGkiY)rIq}JX54EKuES=@`y}o5h5i5&X96P*o#%af zJV0l<@}o4|;RC)ae*NGKfq8mf{7HXekCBmQ^)se}Ql0R(x^@Waox0A)F1Ay|p=*CC z%w0v;rqThOnYKZkU-rNqw~Kcz*}o8VAdGLA)&t{H$G)dao&5mCJ^>ZZeGcU(`m@W~ zyRK{Kyc)%T3$boS8Ecv!N(uO_I+*H>UHWlK9T;ce?nm1#=Vgk*EiZ9W~Nw9aW;w zEIy02<=R(Q(Fe$OezC8CPM{kXY~qq13>SZY<*&!dUz`@5-#>VFEBgc;pC>qa*dVcY z*ZYask51c+8#Z(Sow&fhCnNd>&`0?GwynnV5#s_J8+5xZFyY!OjN46IK6SqbU3gr% z_KCRzm_wvnN2TGvwf66vBjk8JV9o>6joVGB9VPu6I&h8oQh8Aq_)ods=+J(%^9V6d zFv^%tsl5DcN8JH-v*@u$GV247cIY52_ISmZA84KHUCviyy@1Yn(rL*XThoC)uNRjr z&YTZGnqBGdtHgFNZ-2?6KO4Q^pXYSd=Rl_jRGA+09XDkNna zU=iyVOwRSVYYO)dNGiWo#0ws>P!B*F)69OcE&Rvj|0X`%z0-(a#~vaWmn@E--|cAU zN6iP+htu32fV8Et&L5P&*oz2r_)XNUu$LJ36LgFDQrkh@F^%H_NK;nZsAJ9>Y^+}p zdx~aN&!sH6{2OE9X_zB~G`KvzugZonUk79MCh7q*etsL<0glfNfZbtEoXc~CA-iky z`c&~=E*y*vtX}@2b{~MXLYIxcfIc7vJ^<}ytcx+(mt1Wb1z90;*4p~zm@mDC`8ptaps9}ve6Kt>za@D}Sb>F=lqV1KVT`mN+0%nf2+Kze)t@)CRF znAn4>5Ir+}VSb4T`w6o*$N*WA z*sm|DzHEIC;{v+J2C=8oFU;H>F)G7mFQ2O$4$`gx2C>g?0SUhWs3(@GzZavwnc-Q*wj8|)#Xb049Xo_m`7 zlmCj_2dw{_^C{R%Ohuc`FN}%fuc8k~u@7MRZ;n+ReG=#k(7C7R#<$)OIMYOj>_(VA2?sYuMS`Bb&LnH4v-EzDJ^Tre>(CN z?EszU3{4(?4eJ0YtOJn$bnp+qjy?T!oym$dh+MCg9$WG10pvejdHUG=-)UW%Qcq==26`8AMcI(@kOiyq{IiX z{7;@$9rXe9)o*yCQ;QE+_S{qCpZxpU7R!G-e2T6)K^SAaU~n^iKni>S%YQpq)lmn) zp6c&z+UV=2t7!xLX6Ak5pZu4#EtdcG_>>3kxV>y1Rl`2s-S+kd&Iu;}Wo?W6+r$6h zGn)kV^{tloa_^77w2gHD$9(|n|Msw|qYZ#CfBoPLE&M;#{wVn;|0mcM%YVCk3f2dI za^S-g@~K>zUdgqtT_1=+r#H|CIP3#h{@cZ>t~!9OJwhg3e>M3h|HqXrEdTBEDLCA^J2zc9mi&|dW46We-%g*RYfV7=ALfyN@=yLPw*j2#wR^{V$MPwxT#tJH zoek^AzvX%FSEpqC-_EkyFTbk2{`kWSl=BZiF?!e_`Tz%g0Qt`r|JPr3u@?TPPn}Hu z$$#Fq*#5WIr=YKX|DIjSeZZ0@{zU%CKlyjS29_^l=Up}5Z@vCn<@}?aFy_3$tOGdU|2^9w|JmgKhvyE~!vDnaSCN16PaCk@ z22fAeu^#wW_us?3w>CBk$jMZH@9ez~aXjl7I3~8*sn|bnFN4+Nu@I zdk6B~s{^q7&u;lYclLwI%m06E+f4q+KW)GP8_;o1_~DNa2<$W9l`s6XyL@5!Z+HIa z(;@=yM01D4vrnBhYN+Wwo?zomtL z_<(<|dov#g;?h}%Vc6A`Uo8Kt)1NE$hc!R9-f*2*v*KlK%=@hqLY#;jGPt6ztjJ0J z)6GBn;_kV9lGw6wy>aeK{Q+cvEbJe&$L7etJ$a$B%o)&BpD%MauV0rq_oaRSGNC>I z8C9m4VO4I+^Is4kkxj5rddAnpC0~O`}7oZX3h`?4|G{q z=KJ{q$O@Sudzv}3HErZS9sFbe{k5;Ia)$d-e*oEehJblnB>(1huDUFkaP1Xh@2>Zq zW#97!NCVQMT0acW-^R(mziw=|8}y&ed0@Kp+)$3F>`v@J9}`)-&iMgq?Unqi)u-yR z1asojFt#CVv4x*!t=%1r-jeE!!vjm@8De>=ZOXR z7wCv%g4NkC`L9lYs@X?)*c&Iv?p5A<^Yu02j)^ym!DlvAU7to62n%7VlrbnQ=t@T( zWF<`UZx{dA8_&d^U5I1CbysRBKT#$lY*XpreuD9BnEc1rosKdB`(p-`cPj2ZI{$Y@ z=G0@}KH^d#b5I!2nU1{3LYU;=9{#aEw$6Qe5ANS5rrc!yY!1YUxPut3<{os$y~X3% zG5L?DHyvez&UQceyYb3P5-TSWw@x3xet>v(O#b8PO$YyfTe)0Y8JR@6U#cU>P)#`> z^e)B%O5@}2aN8#T;W{#5M`MN$(Z&Ya;3m=#B@Uha0nlL-8TiasHc$S)(nXSXf%Eu+ z%D1@p&(_b?7;n_WVtAerOdgAQ#=tL^fT z|K$1CRrkj{92@7yK_*>ugxChC%$~`AW%|zKS`$x;TJrI@Sw7m%3z5W4Po$dH%O=eMdR}$XDC#Rfp|C zm$s+dCi!&afeW^0OCF3E*jG9Gs2^iZo-JwglLIma)#c;fgHHY8ht73S^n>yI#D?T*q0;9`u(JB9y03KLj*ca zI&H4RPyUnTf9@>x^M1Cyy}=p#hpa(md7OLDsVn>?EhqU;mjCvL<}0`VwX0uo#{MB| z9L9s*flia=%9Z%Zf3o~P)6uTn{#U%P)EWDStU)Ztxd)xP!e7#IlK*7+*D?0L>{tQYfAX)=o+=HC{3pvl>;J6(SIVr)=gEJv{Ima$ z{eLQXt~4z2pDh0z|Ks>yrOc{)p8O}vKj;6bKSPM~f1=wo`H!wEm44R?i}QcG&+`lF z&#&|z=l@2xY4RUkS01w}Ke_%#J^x()6WylCe{@}`F~we|5F|R-2byOdnW&t z=}QOy2$TDNSMLK1?EigKSN<(c*(yV{Of81VCSttxt+mUGb8Cd-&)1-|_j^eg1cz z{f4@ib#a(fxa8j+{#(D@Lu}c&Uip3)-u&5v`}c_?&H+c9h})Ax^(S-&-DzbA>^Idv ztThhV4{SPo@^4<}L3w=7?US@|;&F%m025~dBMzPCeS7JF&VqERsGX)HtOPjs zlbih8#s68S_0)Dgw#N%TaRxWe;ni_ID8fY8y3YA7O$T)5XWz=MOZEr!u5VKoo+Unj z{M*Gp>`P}qKruV`j&pc%7N?4{0uctn(sj0PsqFZzyWQ{c4)zE3=+Wf;PRl3xw~v36 zpXkpnXYabMVZ8us0cUdIT%IWBhu|4Ji+Av@j`KW&(hD8wsK1nB8|>$Dxf*%zP`UfA zUL*hZ@ef;S8#z=QJkVv-2Nbi0b9rz!2lnU39^Kfd7k~JUYq+Q5?4Dv7g1$pH(2*hT z4oUX&|5}>rmK%!GNZQE1o&3Yb=FFUt82>@+*>DfKF~*_n`}g>7ZmPe_SiA=M2*|&^ z{I~Y$Db~KaiatQL^NW2BbOPO&;tP^YCDSA$M^n?bz`HwYrk!gfBW+YwuW)8 zMUOp_Ss#G3n;PpYW*e-R^55E;J6S&SY4LNeekcEK@DKY!pVx~^7H7@}Ak8NG`-)ip z6s(u>kKgi{g+ z-!1-Or{EzA^#G(XLH0#SzyArYAPxHgkbn31hYvt~IL-Y5NLxa6{(M~?Y@Yyp$%W>I z`f)z>24~s>OBVfEd3zFJx~>-&3Mit`+7m*9TF&(XQu*X28Vir;hX z`@#jkRn9-cbWIN~%ZoAbG|Uk~8qAOHm#zQp0ox}4U*>|G8#viz+qrYn&C2;lm@ezY zMcFYY&gHqnklpmWKEHgHW&flysF%GoESLL+3}?4rShimuc~(EA>$tEc3}M=y?lj7W z_A=JRB)M*|(s2aH3Yn8^>zCNVZZKUIcAtx`_XE7}^pnc!00^r@pZqQR>rCnd>taps zA5baxcpfquT*DhF52CdD%eaA>mWH~2n9O69ab0}=F!9NO53BY82m@iMl#OA<^Bw3v z!`?V1_TZ|NeaHmalJ4U>!L}sBlI@x@e1~3Y?o~TPhWF(OVH)=RapRwT?=P$7A7R9e zpYO%vJWOxc8xQ+q>fBd2D*uoHvKZN~uX66b1jC`A2RtD2VqRE&*m@tnxc!g6kL?5S zu66#ravMmk|HA&*D06k5G2-V35GUf+zDIX;`8_WH?MSzVH8p*!rKx_Wt&E4s1%6S6 zSJD>1vv`N>>nXp(@+{8%F~f%loRz2R%u)COgpD{5R~#9Qz9X3hyOAIdd8oPZ)bph5 zUl^6YI=^>9{-6zye!QUl-Ea@ju)XgN_vLI7eBM88s(60!BC&nzJF)A3co$(HEGL-% zikUCKYzWpB-IjX3x16qByM}Q*jOAlK59ajY58rW(V|;FN?`Ml62KE)>#1fZh4ZiUm*KiNd;90!m7oG#x@2v>#Xb;Ohg6dW{kam~zjk14LhEW&s zbg@F>eB9nX(eMs`>brGyr%3*H&XgJC7pXk@?l)lnVj?h+|w-uJJ z@yb_Q^>zeo#wR=aoBbP39U=MuoIZd)fHq)-4SWtJqZ3^HlLLFzUnKMXDD&Tn{I|7C zqFnmx7)QZk^+Z=5LUUu?CG-L0pJhN|HgFUSR!*+EHwyItwgvS2d6uQraj^}2o>aMC z>OBhm0qhf`eHPm%zw5JyBc|K?rOcQ9E{qFsY*1gGlPBsHHn77~KYuCXVZRIW12{*B zc1mBR#|BpD9^VgRJi>j<6U1C$r`HRxPb;xLE&7fVeFs>=oL(*S`XVr0@gw;)Lk_PS zB>&{!3LB6Nsb0%dG0ZVv$gdsl-ao+EJw&XqffLIEmXYb#37DCuY7K8pgq6IO-$%&d zy~OAPSQjvA1Co_$_w7*vuG&Cq3VVv~Hmb|?mlLcDq$iJe7xwCmwO^lFT=j&8eTWO^ z3pjno2z?mq1PX0n516QQk8VALnhABZJc|UrE)C}mDzpQZ<>@KQL-P{04+g-38S3ne zrEu;r&L01iEU{coxedU^7lxg~YbU9CrD|=>o!q~%Zfr|qZAVLE{o$0`L1_8F^ryua z!j@}0VB4^9y%eP;^poHtWxMFEmZrMpl7&O$AuT+lJ{^Z(v#{MlIq%H89gDq3&A=W# zng;Z)Z)<6&ohJ7re^{PR^m3v9V6XHIpUFS&N06cRb3)|-Yz(%xH*b5=2D@#sb{cFG zHYyo%rvF~xUspNmznQ;<|9x5Rb}l!{d!X#=x!kb;W|x1zrtrQ`@W&$f-}0~La;J0$ z(CK&oYu)!}``zyr;Q#z?=W??UfUkH30e!_Q2-xKxeqKYJ{`d0Gs!u8~P<>K? zVfO>oo6DUMc0W-4G6Sl;-xaK%Z-(9P41WJ>)!v^S9Da@N`@zw>Qx1frgSV*xYJeJ` z2B-mQfEu6%r~zt#8lVQK0cwC6pa!S`YJeIrtpV-*yR(B+RMQ!Jzv}+&uHf*$8Fs(g z{{OJ|!^S7V?uU(!gxwDtp9ve^3B5l%CLGR1)vA_(FBcOo_mg94Q z3LF(aJ{YJVIld@n`zIhjKH2GiKR>?N<$pgvJ{q8a`~Yx30%h_76F3X^{S%NEKOl$m k;s@kVVSL(Gz)|_=eFZ2)?=w7KG`{W=ffu|_3c98Le=D=U=l}o! literal 0 HcmV?d00001 diff --git a/canvas.py b/canvas.py index 0f6a3c2..c5e00ae 100644 --- a/canvas.py +++ b/canvas.py @@ -529,15 +529,21 @@ class LedCanvas(QGraphicsView): inner_alpha = 0.9 if is_active else 0.25 bridge_width = 1.9 if is_active else 1.0 bridge_alpha = 0.65 if is_active else 0.18 + arrow_width = 1.8 if is_active else 1.0 + arrow_alpha = 0.95 if is_active else 0.28 inner_path = QPainterPath() + inner_arrows = QPainterPath() for block in line.blocks: ordered_items = [self.items_by_id[point_id] for point_id in block.point_ids if point_id in self.items_by_id] for first, second in zip(ordered_items, ordered_items[1:]): self._append_connection(inner_path, first.pos(), second.pos()) + self._append_arrow_marker(inner_arrows, first.pos(), second.pos()) self._add_connection_path(inner_path, line.color, inner_width, inner_alpha) + self._add_connection_path(inner_arrows, line.color, arrow_width, arrow_alpha, z_value=2) previous_block_end = None bridge_path = QPainterPath() + bridge_arrows = QPainterPath() for block in line.blocks: if not block.point_ids: continue @@ -545,22 +551,48 @@ class LedCanvas(QGraphicsView): block_end = self.items_by_id.get(block.point_ids[-1]) if block_start is not None and previous_block_end is not None: self._append_connection(bridge_path, previous_block_end.pos(), block_start.pos()) + self._append_arrow_marker(bridge_arrows, previous_block_end.pos(), block_start.pos()) if block_end is not None: previous_block_end = block_end self._add_connection_path(bridge_path, line.color, bridge_width, bridge_alpha) + self._add_connection_path(bridge_arrows, line.color, arrow_width, bridge_alpha, z_value=2) @staticmethod def _append_connection(path: QPainterPath, start: QPointF, end: QPointF) -> None: path.moveTo(start) path.lineTo(end) - def _add_connection_path(self, path: QPainterPath, color: str, width: float, alpha: float) -> None: + @staticmethod + def _append_arrow_marker(path: QPainterPath, start: QPointF, end: QPointF) -> None: + dx = end.x() - start.x() + dy = end.y() - start.y() + length = math.hypot(dx, dy) + if length < 8.0: + return + unit_x = dx / length + unit_y = dy / length + mid_x = start.x() + dx * 0.55 + mid_y = start.y() + dy * 0.55 + arrow_length = min(7.0, length * 0.28) + arrow_width = arrow_length * 0.55 + base_x = mid_x - unit_x * arrow_length + base_y = mid_y - unit_y * arrow_length + perp_x = -unit_y + perp_y = unit_x + left = QPointF(base_x + perp_x * arrow_width, base_y + perp_y * arrow_width) + right = QPointF(base_x - perp_x * arrow_width, base_y - perp_y * arrow_width) + tip = QPointF(mid_x, mid_y) + path.moveTo(left) + path.lineTo(tip) + path.lineTo(right) + + def _add_connection_path(self, path: QPainterPath, color: str, width: float, alpha: float, *, z_value: float = 1) -> None: if path.isEmpty(): return pen_color = QColor(color) pen_color.setAlphaF(alpha) item = self.scene().addPath(path, QPen(pen_color, width)) - item.setZValue(1) + item.setZValue(z_value) self.connection_items.append(item) def _selection_has_assignment(self) -> bool: diff --git a/icons.py b/icons.py index b46bf18..31d74ff 100644 --- a/icons.py +++ b/icons.py @@ -14,6 +14,7 @@ LUCIDE_PATHS = { "file-plus": '', "file-code": '', "folder-open": '', + "git-branch": '', "grid-2x2": '', "layers": '', "list-plus": '', diff --git a/main.py b/main.py index 520ef9d..4b4fe2f 100644 --- a/main.py +++ b/main.py @@ -1,15 +1,28 @@ from __future__ import annotations import sys +from pathlib import Path from PySide6.QtWidgets import QApplication -from main_window import MainWindow +APP_DIR = Path(__file__).resolve().parent +if str(APP_DIR) not in sys.path: + sys.path.insert(0, str(APP_DIR)) + +try: + from .icons import lucide_icon + from .main_window import MainWindow +except ImportError: + from icons import lucide_icon + from main_window import MainWindow def main() -> int: app = QApplication(sys.argv) + app_icon = lucide_icon("git-branch", "#e5e7eb", 256) + app.setWindowIcon(app_icon) window = MainWindow() + window.setWindowIcon(app_icon) window.show() return app.exec() diff --git a/qa_smoke.py b/qa_smoke.py index 8d043e6..52d0cf5 100644 --- a/qa_smoke.py +++ b/qa_smoke.py @@ -81,6 +81,7 @@ def main() -> None: test_canvas_rebuild_can_preserve_user_zoom, test_grid_toggle_adds_grid_items, test_canvas_connects_new_block_to_previous_non_empty_block, + test_canvas_draws_direction_arrows_on_connections, test_canvas_rebuild_drops_deleted_selection_items, test_canvas_refresh_does_not_accumulate_connection_items, test_main_window_has_classic_menu_bar, @@ -767,6 +768,32 @@ def test_canvas_connects_new_block_to_previous_non_empty_block() -> None: assert bridge_found +def test_canvas_draws_direction_arrows_on_connections() -> None: + if LedCanvas is None or QApplication is None: + return + app = QApplication.instance() or QApplication([]) + with tempfile.TemporaryDirectory() as temp_dir: + path = Path(temp_dir) / "arrows.json" + points = [ + {"id": 0, "x": 0.0, "y": 0.0, "row": 0, "col": 0}, + {"id": 1, "x": 34.0, "y": 0.0, "row": 0, "col": 1}, + {"id": 2, "x": 68.0, "y": 0.0, "row": 0, "col": 2}, + ] + path.write_text(json.dumps({"circles": points}), encoding="utf-8") + doc = MappingDocument() + doc.load(path) + line = doc.add_line() + doc.add_block(line.id, [0, 1, 2]) + + canvas = LedCanvas() + canvas.set_document(doc) + canvas.set_active_line(line.id) + + arrow_paths = [item.path() for item in canvas.connection_items if item.zValue() == 2] + assert arrow_paths + assert sum(path.elementCount() for path in arrow_paths) >= 6 + + def test_canvas_rebuild_drops_deleted_selection_items() -> None: if LedCanvas is None or QApplication is None: return @@ -963,6 +990,7 @@ def test_lucide_icon_names_are_declared() -> None: "file-plus", "file-code", "folder-open", + "git-branch", "grid-2x2", "layers", "paintbrush", @@ -1356,6 +1384,7 @@ def test_main_window_restores_tree_state_after_refresh_error() -> None: def test_package_imports_work_from_project_root() -> None: + importlib.import_module("app.main") importlib.import_module("app.canvas") importlib.import_module("app.main_window") diff --git a/requirements.txt b/requirements.txt index da33986..0c6237f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ -PySide6>=6.7 +PySide6==6.11.1 +pyinstaller==6.20.0