From 3d0f39f846071178f0696f2bd55680c9406edc8c Mon Sep 17 00:00:00 2001 From: Yury Shuvakin Date: Fri, 9 Sep 2022 16:36:44 +0300 Subject: [PATCH] Various changes have been made as a result of the discussion of the first version --- ENNOID-BMS-Tool.pro | 2 + qml/Controls/ChartView.qml | 129 ++++++++++++++++++++++++---- qml/Controls/ImageButton.qml | 27 ++++++ qml/Controls/TextField.qml | 2 + qml/Controls/qmldir | 1 + qml/Icons/cubo-icon.ico | Bin 0 -> 100508 bytes qml/MainWindow.qml | 2 +- qml/Screens/AkbMonitorScreen.qml | 6 +- qml/Screens/BmsSettingsScreen.qml | 4 +- qml/Screens/CellMonitorScreen.qml | 20 +++-- qml/Screens/VisualizationScreen.qml | 128 ++++++++++++++++++--------- qml/Utils/MathHelper.qml | 5 +- qml/qml_items.qrc | 1 + 13 files changed, 259 insertions(+), 68 deletions(-) create mode 100644 qml/Controls/ImageButton.qml create mode 100644 qml/Icons/cubo-icon.ico diff --git a/ENNOID-BMS-Tool.pro b/ENNOID-BMS-Tool.pro index c40953e..11e49b2 100644 --- a/ENNOID-BMS-Tool.pro +++ b/ENNOID-BMS-Tool.pro @@ -173,6 +173,8 @@ TRANSLATIONS = translations/cubo_en.ts \ translations/cubo_ru.ts \ translations/cubo_it.ts +RC_ICONS = qml/Icons/cubo-icon.ico + DISTFILES += \ android/AndroidManifest.xml \ android/gradle/wrapper/gradle-wrapper.jar \ diff --git a/qml/Controls/ChartView.qml b/qml/Controls/ChartView.qml index 8bdcb1a..b358979 100644 --- a/qml/Controls/ChartView.qml +++ b/qml/Controls/ChartView.qml @@ -1,6 +1,8 @@ import QtQuick 2.12 import QtCharts 2.3 +import QtQuick.Shapes 1.12 +import Controls 1.0 as Controls import Utils 1.0 ChartView { @@ -20,6 +22,7 @@ ChartView { property int defaultXMax: 10 property int defaultYMax: 1 property bool autoScaling: true + property int selectedSeriesIndex: 0 ValueAxis { id: valueAxisX @@ -45,10 +48,47 @@ ChartView { titleVisible: false } + Shape { + id: currentPointLine + implicitWidth: 1 + implicitHeight: chart.plotArea.height + x: chart.plotArea.x + y: chart.plotArea.y + + ShapePath { + strokeColor: Palette.borderColor + strokeStyle: ShapePath.SolidLine + startX: 0 + startY: 0 + PathLine { x: 0; y: currentPointLine.height } + } + } + + Rectangle { + id: currentPointInfo + + width: currentPointInfoLabel.width + height: currentPointInfoLabel.height + color: Palette.backgroundColor + border.width: 1 + border.color: Palette.borderColor + opacity: 0.9 + radius: 6 + visible: false + + Controls.SubtitleLabel { + id: currentPointInfoLabel + horizontalAlignment: Text.AlignHCenter + anchors.centerIn: parent + padding: 10 + } + } + MouseArea { - id: chartMouseAreaA + id: chartMouseArea anchors.fill: parent acceptedButtons: Qt.LeftButton + hoverEnabled: true property real lastMouseX: 0 property real lastMouseY: 0 @@ -59,27 +99,28 @@ ChartView { if ((mouse.buttons & Qt.LeftButton) == Qt.LeftButton) { if (mouseX - lastMouseX > scrollThreshold) { chart.scrollLeft(mouseX - lastMouseX) - lastMouseX = mouseX - chart.autoScaling = false } else if (mouseX - lastMouseX < scrollThreshold) { chart.scrollRight(lastMouseX - mouseX) - lastMouseX = mouseX - chart.autoScaling = false } + lastMouseX = mouseX + chart.autoScaling = false } + + updatePointInfo() } onMouseYChanged: { + if ((mouse.buttons & Qt.LeftButton) == Qt.LeftButton) { if (mouseY - lastMouseY > scrollThreshold) { chart.scrollUp(mouseY - lastMouseY) - lastMouseY = mouseY - chart.autoScaling = false } else if (mouseY - lastMouseY < scrollThreshold) { chart.scrollDown(lastMouseY - mouseY) - lastMouseY = mouseY - chart.autoScaling = false } + lastMouseY = mouseY + chart.autoScaling = false } + + updatePointInfo() } onPressed: { if (mouse.button === Qt.LeftButton) { @@ -88,13 +129,71 @@ ChartView { } } onWheel: { - if (wheel.angleDelta.y > 0) { - chart.zoomIn() - chart.autoScaling = false - } else { - chart.zoomOut() - chart.autoScaling = false + var scaleFactor = 0.75 + var area = chart.plotArea + chart.autoScaling = false + + var yAxisPosition = mouseX <= chart.plotArea.x + if (yAxisPosition) { + if (wheel.angleDelta.y > 0) { + chart.zoomIn(Qt.rect(area.x, area.y + Math.round(area.height * (1 - scaleFactor) / 2), area.width, Math.round(area.height * scaleFactor))) + } else { + chart.zoomIn(Qt.rect(area.x, area.y - Math.round((area.height / scaleFactor) * (1 - scaleFactor) / 2), area.width, Math.round(area.height / scaleFactor))) + } + return } + + var xAxisPosition = mouseY >= (chart.plotArea.y + chart.plotArea.height) + if (xAxisPosition) { + if (wheel.angleDelta.y > 0) { + chart.zoomIn(Qt.rect(area.x + area.width * (1 - scaleFactor) / 2, area.y, Math.round(area.width * scaleFactor), area.height)) + } else { + chart.zoomIn(Qt.rect(area.x - (area.width / scaleFactor) * (1 - scaleFactor) / 2, area.y, Math.round(area.width / scaleFactor), area.height)) + } + return + } + + if (wheel.angleDelta.y > 0) { + let zoomedInArea = Qt.rect(area.x + Math.round((mouseX - area.x) * (1 - scaleFactor)), + area.y + Math.round((mouseY - area.y) * (1 - scaleFactor)), + Math.round(area.width * scaleFactor), + Math.round(area.height * scaleFactor)) + chart.zoomIn(zoomedInArea) + } else { + let zoomedOutArea = Qt.rect(area.x - Math.floor((mouseX - area.x) * (1 - scaleFactor)), + area.y - Math.floor((mouseY - area.y) * (1 - scaleFactor)), + Math.round(area.width * (1 + (1 - scaleFactor))), + Math.round(area.height * (1 + (1 - scaleFactor)))) + chart.zoomIn(zoomedOutArea) + } + + updatePointInfo() + } + + function updatePointInfo() { + var currentPointVisibility = mouseX >= chart.plotArea.x && mouseX <= chart.plotArea.x + chart.plotArea.width && + mouseY >= chart.plotArea.y && mouseY <= chart.plotArea.y + chart.plotArea.height + + currentPointInfo.visible = currentPointVisibility + currentPointInfo.x = mouseX + 5 + currentPointInfo.y = mouseY - currentPointInfo.height - 5 + + var currentX = Math.ceil(mapToValue(Qt.point(mouseX, mouseY), chart.series(selectedSeriesIndex)).x) + var selectedPoint = Qt.point(0, 0) + + if (currentX < chart.series(selectedSeriesIndex).count && currentX > 0) { + selectedPoint = chart.series(selectedSeriesIndex).at(currentX) + currentPointInfoLabel.text = selectedPoint.x + ", " +selectedPoint.y + } else { + currentPointInfo.visible = false + } + + currentPointLine.visible = currentPointVisibility + currentPointLine.x = mouseX } } + + onSeriesAdded: if (count === 1) { + series.pointAdded.connect(function (index){ chartMouseArea.updatePointInfo() }) + } } diff --git a/qml/Controls/ImageButton.qml b/qml/Controls/ImageButton.qml new file mode 100644 index 0000000..c4f1a67 --- /dev/null +++ b/qml/Controls/ImageButton.qml @@ -0,0 +1,27 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 + +import Utils 1.0 + +Button { + id: control + + icon.width: 24 + icon.height: 24 + + contentItem: Image { + source: control.icon.source + sourceSize.width: control.icon.width + sourceSize.height: control.icon.height + fillMode: Image.PreserveAspectFit + } + + background: Rectangle { + implicitWidth: 44 + implicitHeight: 44 + opacity: enabled ? 1 : 0.3 + color: control.pressed ? Palette.pressedButtonColor : + control.hovered ? Palette.hoveredButtonColor : Palette.buttonColor + radius: 5 + } +} diff --git a/qml/Controls/TextField.qml b/qml/Controls/TextField.qml index e5af946..c61f75a 100644 --- a/qml/Controls/TextField.qml +++ b/qml/Controls/TextField.qml @@ -9,6 +9,8 @@ TextField { color: Palette.textColor selectByMouse: true selectionColor: Palette.selectedTextBackgroundColor + leftPadding: 16 + rightPadding: 16 background: Rectangle { color: enabled ? Palette.backgroundColor : Palette.hoveredBackgroundColor diff --git a/qml/Controls/qmldir b/qml/Controls/qmldir index 426b3c6..29ecb8f 100644 --- a/qml/Controls/qmldir +++ b/qml/Controls/qmldir @@ -24,3 +24,4 @@ BusyIndicator 1.0 BusyIndicator.qml MenuItemDelegate 1.0 MenuItemDelegate.qml ScrollIndicator 1.0 ScrollIndicator.qml OutlineImageButton 1.0 OutlineImageButton.qml +ImageButton 1.0 ImageButton.qml diff --git a/qml/Icons/cubo-icon.ico b/qml/Icons/cubo-icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..74750a70404e927bd867d6dfc46cdafab30fc8ec GIT binary patch literal 100508 zcmeG_2{={T_g9lN)0{-3iVO)wlu}9>Q9=|+QdGzgB@L7$6`7*JkeM<>hGYy;8AIkF z88Z{k|E&A|$9LZCTxodk^?UEE@4I)OVGnEXwf5d?ueJ8s6bb`nG-b*Z3L0`yj*g;G z_EIPm4vs(WAEVD>(P!(||8c)`1BG&AI)!3x{{Q!587MdQp$Slc^fgN9j z8Er8dVYOkU<~8xkL}Y>80)*u%3o`5aEQIM26T)o8Ono-?(pYMoIW}{s_oFOEQSbGm z^pWDGVr2f!`AAGj3}X1)5LxHG4uSevCs`wc&VopGT{aR?6oKsX+lPb~g;V1|S%5=u zK`?S4@Bni7$zde5DiyIxv_d#;a!}(R4Lypeho~d|dH%@3;DglsqD!KYlh004(=wPd zAlH+xBh#-vsm~@` zPDa8C!;qCuE0K%w7m=J#IY?G*7NQ%Wi)e>wBTMfrMVwzdQ`}k;M4u=_0RI)&-Q`R=29wcp-=u$T?Pk&AG?5Yh6DYrM}IK> zPyroySV#Tw`{M!n{{!R9kEVI^@eP89*EO$ytZjY}XIURvP0c4@wLhAdg ziB}Q0Z|Z6q{J~rX<2$?qT=MJlsq-h~3DDl#PEGTy_!%kicFNjvC7WF;M;UJgbmUIp2ALt*<17sF_*>G)_YKMGi`apdS?ONAfhXCE6{FKU+LE3@*1@S?K z!SaDR7B&|CseGUd$ZQ~+K>4+8wS#$Z*>inWKFERhGVf8JgX{pX+DoQpe0rBiHaEYj20Lv3TM{h?gQ57I$dAVWeN$RFf8_<<~owE^(BL%IVsE<6MN zf;ekj*B~IOL6`DKRn3~CwBj(`L94#Ii1 z^9IYu@BqGndIFvpe?nTd5H;k{+ecK{5$iV~(*lk_19%VRLq5~4O+!vco~D+y)nn^d zWng8|hQRm0BOr4Fy}SFmseM-SsV23Zy|TR!;AgBaK>LFHaR1GHDy={k2RZ*!$0ww! zwF*fnPoU;^Gxg>-ae#57wWk%i@ZthBF4S4%o(T0C_y^WhfbWU$6VyHm>k=p*+G@Mk zb}D}6FU+a(JB$V6E{~(ehc*Ym#wnmV{6G$eH4N4U7~g=Lk3GXeShx7)`ca=_;|Po? zP(HK~^iANcnbtEA;6;yj9)o#6Uj~425AxG^qCx$^xFmW{bTA&ggSLcr2VBM7#i=qr zT+=Hb;zC&<_XEHa+RwDq(h+dQ`+P&Ux4xvP0_J;QmMlS-r6*JmTphFBdTguYhP zSVYYW{(#QV59pN-Z2{vX{9p|N{T=!d;Do`)_K(QsN1Ojt4!%sN%lw_?59$H_p$pJctVe(5=x)FLN8M9Us-pc=eOz)O#yB2Z=^lc3FJnY1BiZ|Upfal;on;Sq3Q)P zE(Sxz`?tO$>rc|34f80Je6> z1KJ-0u;pU;;_DCfpyfGQUqqt%gP#fb0s(yP{)~Wa81f&g{``*o7&=s66ny6mxa$8KmLIqG6pWMUA|d=u=QgAKEwF> zW6x>B;n?hjGrYssKOisQt9b|WYr^sYUkVJcF%jN@PXY$u zCk4-jqW=!>9awY({HgKvFKa3L)4Ykzp_so2h6_;$K5iJme1gS+@fCd40Pte~=5Gqm zhtvPTcLRf+-a7~L2j0XWvN&?c`hzS1ep(oSUkLCL20zpPFTK2kWkdC^!|D$q_z+=$ z$q}0#Zo(c>`yt%p{QqD@n4p|r202v_`@E8Z^2OV5A;DyzJR?OSiV@udzW`Gd(2-9-huoJdlx`P zp$Ay~AwIMj_#$F52BZO>EAZ6=9I*0;>JRfodUZMiatE!i6Wm+GTMY665iUd3A7okJ z74YH0_PSuWKnP<1#!Iw*Uhp2|0@!0h^uzr4@cDtiBh2^0F2aARHx>ud(B?&i7Zx8M z!u){w#o~+A&jjU zK)vz#V0;dH?O+~*cW{jX_*`J|;XTM<0N?|RJ;&1@d{f~$#I5{XiDg3_XTYZwTjPP8 z0R0}z148g6#n#L)C%}3D|Ncj=@oUJWilkxXcjoz-LA=4w8((hJhp54KFgC(FeEn^b zZK(1xtf?^o-*#wce7-<)e7@K<$eS2oKGxVf2w^+{UIO{*M#>H9yaszJ@p16i(8d@H z*EV=I<}8+k%E$QpK!(5o_F=+1eEqR^SP1k6|6_;;dmJ#pk01E_Vc#SMum-@tgKJoi zK)b_UOW-f?qsDY=v~l4c_Ge;%@3Ta4v1dP1e^~26-@pKT?12X%Z`e^d+U#1|6qL@=z{@_ zg@6lw9R|8SZ2f@Y39{a+s#jRDujX0M;ehS}^;E%bd7j95avdZHHHG{Gr;RawEWR$z~?Yl5Xpb>W%1NK z63&5BYX#sn&@aMX zeb|#hn=kZXSkuD~)`9r<&p6ZZ2`9Xp8>GN=GEs>eHt-a zKS}rBpY9lqewO}t-WiTxD^@NU{vJH?E9np807=*1gRaA+!>^=2%mu?OmyGv&@X4>F zKgld4TBqOz@p1T0Jp&|KH^ilJ+F+Ngg11faC#^2S^?ud4S{rk_Sj0 zAbEh~0g?wu9w2#uw@MpW?5?Ch|V~rr3suDR@7KToBX@)e>MH#ELAwua<~9>rL7z7J)AlGtKetp59e?Z^?f+2 zi5Bl4p+8nn80UsM;|b2Y{T1-5>5o4bayasqcb1va~PPF;LJ)8^oE8tht|66Cs(&7wrz_-qErFRxAoHGh%Jr4I9R}hc( z{IKtt|LOI8w=6e88quTw>zdb8oMFs>Yv3Px0Cj{jo$)*X=i|~#17`w0|L}a+KiKz! z&--Vub3f&NRTVg&eV^Yx1kUUwf^Y3Jz@NX1r3bkQe`ffP;{84J2VSEEnCs@>oKM(> z^ym*~OXBgJX+0CUo_w9Iyi9v0JMaS$z}SS(_wT-jdJvsg=S{z3UcU*XW**G)ZVm|k0dFa04OIH#Ei z;2dA99FPf!;Gd>H{@ijre)DYS4dR|!m5P5wy#}5be|h|19IT#L_)pUx&W9!%*8usz z{6GZGOT}=-hd)Ap_~ttBHZ8z>592H3|GUz^xT%;HZR#2o-zJs`mS4qz2Qc>GCqo>9Ih;^5~M=6 zP4I0$+-o=kmzyk8U@Z~hcTZB z;4E+2ynmGbpg$!7IFA|1phthAH1wW+rWB{2)1}^A&mdK(SOmc zMMUT$SpUT8`J?oQxtRzK1RnS;>A(2aVj^@ASPx)rfe%67Lj+cdR)q11=>MzMe| zM5qwzbFn&r{*4ISv)uoL|BurDS@AQX><9Hs^v3^xYW)Z21QCH^fa0Ip18YB!0f^+N zS5>bFOI&3hROr7l(t&-ANM}G;@HZaP+W!GZ z=zm$YS>H%W|MkcAj|li;(bkt9{psiQ=QrTzCUIZlt9+rHQ_oKQIr0zQ{jB{T^xs4< z$7T*8-uO0x@s$>!zkzQPJ|0}tlhbL_f9sxz{*XV&D)i348Y<8B-gX3J2wHsM{%7b9 zxPadiQNHjD`VaJ#pM{^HKg>yB+oy$Z+4HbEor^n1o0NLL-D^8xy+Q9n&kjSF5y3-x z^aq?k@0L)WK#+>~5%}MM%!bv6HvAF#gZ&rgG??RQ^P{~7dk~Rt1TnV6Pk)B~{Eqxz zWeW1^P;G**gP5DxSLxxwP&RewYvAY5+S5vy_FE5!qCemUJPr2hq3k`-W_po&2-78| zA!#&R`okCkKC`+Jy2JK|#o7_(F7Tm*`Rhme#?R0nd?E~=8xHXo#gJ!+-zY43fqqjsGqlfU(Fl))WaT2thJyGm#HXAE>@9{(1h? z@f7R^KZ*;C<2$@}P-O+rcb?Sw6Z~l%(jBPt;c)krfc|1Q>t&EtkA@ya*0`?uC3+{2 z-C)1SuE)C&Ssz(M^qwd}q?`EB_W38%!MX+f)xHHFuYj#%xbh;@3H-#)$DKz&W*Q2> zR}=Ko-&%)$?|ua+6MT>*gbzf%ZnA@EcG8F-xYn5v5-su$=xpuK+tK(7uq6QX>7hW>yH^w*(i z1b%T~*Zffc9}2Md5#j#3JcIQR(C$|Ny}dM-Uta#H>_M+Xv~K%Z`U6t*eCfdNxu&h= zr|^cvU;`l9U-rAuUn^8=$U1`T2euBdHNcoQ)cgk-(fSQQ|Dvbw0Xrki#gGPUXhZEi zCfa92kN#(#pBY?7gKP$MB=TN-0KTwq0<&S1 zHkTqmXCj+E(cXWM@9D{2@U4iF=8~__g0YykoxX?ukoM{Or(fj@59EF22lEEmot~}O zHpLbJ{AlsO?qQCC@r($d&MTc((oKuy@pp#s?Fm|dbshMy4+jsRA*`!`|LMht`GXd~ zKZjm?cn;swAOdU;4VLzM=nr&*@rejFJlHUZk6El4Q559S+ed?Ou=44JAX5;5eVYBS z^PrdauX+w`Nd&N0?pKuo`M`Rc7QlyMs50pFAzC?Vxbz1(o}TXy_^sjRW+M3pWQ1wg zrVWWRzCJKFfNh2rU<~`#IvMf-8w%Jf;2P|j;GY6@0G%9M!@Rl9eI0T$_2#f~!s7?m z^!#}D`tAMUJZZ~-d!ltS_#F_%#h%gg>7msH(4#--mq2#_zqXz>S~C3U9_9qBT+o3K z?Gqkq4;q%1HUv8`5e!F92sn9Vdr|RPc4rwi8?ar$9wGqHV}eWz-wdKHcewZT_WRQI zvEk-NFW#@DKdn4UkN(s~8pa>^FyZS9^BEC^4*?%`43A$C{;u>V`gRe_@k9Xf$oIAdd>@YphGVlQqW?C}ZPe`G zJ1pfb1`z+Bpg;I@g1r)c^nCK6F`-TIb%OO9k!@QqkCQi);Y;qv0?(x|}y$AnGs3(^8dqb!z(Y`-uFM8iZ zpe+M@=D|;j_BsCkpP)b3WbrtFpA!)f>6GaC#M1NS#g~I!!#o0ZFyb;S6h1+I@~Nwt=q*`1E1%Ap9%oPw!g`;ERdJ0j`PGO};}47t^z@^U z#vsbzPwn$p(jRC=WWNC4Zs1X@JwZoF1n|8>EDk=zWG$jJ@C@c&;4%2-FYI}PZ@z>V zg%9b&3;J<<8NbUl*yd9zQ|MLoyPm_mMXMkGE9nnycsKnnA&wx&!9G=bV=*M6{pK&8 zCO>Pl#v|pL>H1sJ9;+|tKZ$Gs_&R*+8us)P$+mwr{h^-|jRD`n_ggwhkQwONA-h$nuyxhsS?7*HA}#bK74{e`r6j-wZV;(bfU%Ps82o0CUIpn!s?yLii3W(Ktnm zH~t=UC17`f_4l^`b+QU6bf0F(`GMA8P z{`dHX?En9JH2anPjHEwFe_9@ZZ^sgSTjN{LNW0kS*|^Yk{k~c1TSeH)kAL@rhfvNbaFf7|=@w%GfbzpnUrA z5ej8#z*b2KIiteH4~7bI`$`yFnls+GnP-Vx~$&Wu|cWEwbbr`=!1rM7oztVqB82KDJ2 zlB4t~srnuw?*!wu`JQIWY|pQqetE-u29CG-Vq=Y6>L@P?%fcMrYF+WF-!OK!fRFn{ zb}vKMz&evD8&<6sNzuI_x!{3=(0sK;jWq=};{BJb?&Rbp?#(vfxOCJ?PRjRDod_CH z(zk9L8d#1npThiz!XcUHC_>qr5|~__k$%jl;9jxloJfvKR!p*#MDgoM7tS)SI-t2> z?9Pufu;@!EW10EKQ8q|kAI#yedz8JO?YZIu^9?UOPwr8<s#GV!k~o7ydc4@aj2cc$1{%f2V3D)sz%XSpZnPgrd)cKghjtHxKQ z-MJFEMtSvmxu&_DHJT)>r_vE=c9_b7g2)P&%$WOVyN9Ag$ z#VX~@=@7jm4G+_k7tpR)5?j znFZ=5F=#dPO;!md>2B!0ck+(By^pdtr@PpNFgro-CzV|`DM9(?&+Yc#@v-ZA5Rg+ig72+Bd|zN2e?Y>j zO!n}?EsOVz5*fYV&9W&wx4Ek&curK9{(M~0^rTOwqKZ4Ed@uGKs8yGEVX?2YmD_`5 z*_hBd<_rO2%3j2bbezLKeSgyaV?NJsM^3HjeVuga%+!MqH6OO0I?OV=Z*E^%ZJDL+ z#n>dbqO&b08X}(ccKa%pScc1{3&+VVNK$AH+2ZPCwduo5pZ4;gSMT1is2U*Sx{eel zRCV5-Z@(*IOiP8*x+UCWxJ&|eeM(Tx=7^AZCepFoM5JR{Xvq?jB?>IhMj2`rC;4YS zmFsQE_gI)KfmXl5JF)U$s>Et1QKnv%IV0oOt(bIVlGPmc%c_=r*SwZ&A1%65Ap1zS zR@u(gD|^QVF=Wq*RE(;4=`p@Vucfl%S$J~zYW~hqo5R=^Z;82hz`|~|0JHU3?jt&% zp3W&S;m|o}I#srCCv*GS!X0lYV~@?g)V=KU$Gm8l!jfGmb6L5w*xq<8;@jw2=;55Ubp+K3USF527x<8pPxZn zZ*kO=GBHXYTi@sOo2vI0=5|jRIje5Yd=zcP{o(KQ4VOi2IT2YYMXUs-U^eY z9g1jAX<;t^SX;O=z#!1p&y#D%B{%cS7cP7GJrs%_V;D|HSuQ61M6gcW4X=-C7SOYx$1Plr^-W> z7Y-zx;kVtcg~C6jr;laehl6I)~Dk?@7XUuJ5$p&B|&qt9Xc+xemY&?5rmGdBjuN;P?ci@#f5K3-9P;CZY4lm2IrSNYlBsL8dC&v&WAdZ~F1` z{38Z#qk{~i9kcj7`=>QTN6Px>t~rq(y;aXkdu4s9f1Fvjs))VGxCiAAtY%G*uTxlg zb;5-m7skd-&GdVs@F!%0TIxcP8YIkpGmCLGu{TA&n^#_9P&-hqTa9QttL+$(So=;gF4r}h{ zkBx9Et}vC+RnpDWh->;RI5%UiW2t&Wfe37_v5{C!U|2tW^_GxqP*_SnI&f& z^DguY9j~;v`Pj2@Fil#$qEFgjcNtZ3H^)2c^aK65E(>$-QJ zf7$Mwu+@@ZzjjxZea$knk{k=UQ3EViT>JM8bP8PFaKS2ZY-nl0JoT3;k5}mh@f)Rd zvt??GPan6&{`S^LLyOs;=A2$GoUhB%7FxMq>c}DK#A$MAT=G$yS%Z#CB5eJ;RSsmf zM_v9f(^p^Rg!gGmyViR~o*6u@>7^#8$8R*8s4=i9$Lqd71u6J!E4<|KT$#0FOKoC} znr*w#IY+|L&r9Qa$1~fGee2&Iob4l-rmtkDbgSs#Y&M@s$tJcMCaT&Md1YU$OWV%e z+u`k&+-+9*VAm?si7KC}bFAx{pNH&@oZ8Ya)U3zjw`(ci3bi?Uvi{<0vh=2e^G?v~ zuT?u$rRM2;)rPHf>is35E-zN4@y6UQkF_5N?00gTa==ud+5O(G#;p=3&Xg2iEy;}e zY}YL1nzn5b%XMSJ2@acA$oIOARGKWwS}!y)w)AoPQgoJ?&^Bw#d8za0%vYXObUv1U zx{|7w?XKos-32`y5sPMu1~#vMm%Yfd-fIs9VxM8pi0pgdvC+zS%A>Sp+6LS`vu-SA zuk3%Ow(!EJO0(q3F9XL*qc~-E2q|3YV0s%}*~YOib+obj_?^6od|8K%J}kW?+kF^1Trs^h*^=Fe-_>My z*CuBsibKk}oziY)My;phcSsq%L5GM{y6;>gDp!a!{EL+}jW9AWk zX9li~TqseZ`6FLz+#5g1Mr4$swxNBV;I6uww?#M3QY5{l#^-q~bVmE~xrWFv@A%VA zc}=cgmibQ4U2W8Szsw-c&5W(fy`)w-)@D0vrbf@MWkHEfp#nhz&mWmd=^xEwpxjh( zym6;*no93Gxp5SS$f8<-%<=j@R!dT+pXR=Cyt|0KOne80veJ5`yvf=O3yl!@v$AKy za_S6D%{~=0aGtkTGDD*|$6L$cXzj{pU>guH}7nRCUiXih{E?+OB;nvmeTDh~Dt2 zVe&zzOB&k8)9cmKqRRYkH?aU3!}* zGh;0KJ@FN0*QjGw+@%zzExak!-RTve|>v{Yu zJIwOWObSf%O>@kXDl6)_-WKE#neRJ;l73wDs-(s2dq-N%8yMY^Pc%#ClFz@XX>9WH zo$PDNifd{Okui+-gbZHtGR5)5?A_;UecwSMY~Z@aV_)}+?)L*IGsn-AX}^~m+4r7N z(@A7y{nNIZ9nS&`FBX{yOL}dN{rptD%QhZmY~{SjX#N++-qvjj8t9iOzqk((FY97B zxxd!J+jQ zYiR3ZdLV#x^t6JxaZYs#>?WpGxe|W!8PdU zH6`#aEh;r0~J1l2415AYTNTnsZXwQeX3bVrD^)--bCvw zAJg0UJx6}=x|JN2EaO*kG@*|vPbs|DZFf-Ivw1tE3pQWMY;(69IiJx{$K}|qt%qTK zP;}+Ex}uH3gIxzVwU6`6Dw@^uE>@B4xwB97y{l{wlcmZp3b8LW%j9$CbDeeeVdkVY zvSPB_pJKSZ}qSHphKqirSLIgQskhGPcLtX`|~YD_|xT76l7tH@RE3EaWa`KLWHcErEP8(F{N?DFbq zwI&L)73ZtTjjA2J3MJN6bbSm4_`c0QhD-t2EU}`ri9XX_a4*H8lxQzT^qY{XKf80m0LD{ zY;nMcQzt%!2|HIRznXs9b<3IyViBDjH4+n=*2N&lDA#2cY-Dv2`uM3}`ofsa56@aR zNgGU0aDNPjxqtVf(U6(6nRh43= zV0Yig1fPcR2SrnQb(VTRL0Qq?a4w~#;#7%LEPJ_XJfhZgwX3i!_uyFl<9W=QoUZbp zbgK=}PLgWma3SNN3@Sst%?O>1u5B*5*t?|Nv&7cUb+c?3bWzpTwvl&RpGatUMzdVj zxO|hVDwzLCxkk~)tBgz3>T?4+9PFe$o(c3%Qd9~%A|8t_%GFdBJh}F?CA59nahnt9 zO5gLHEhlG#ySe++b2V}fiz(jf-vm+J%tCS^f^s}6MRt5{jV_t943 zVNc7*wTzS&wJOEZMl<<2_Y&`-bAjXe2d6L3y*){$;>1TzzsCFsbtI;L&YFlP=oIc~ ze0WKlwPpxgk87~lTwjn6_xN)%bhDXJX%($RR#Y{DJKyH@Ao zISd2z1o%M_%Zt{zK zJ5|5v$b~Z(+b?!Jb97GfC0ifEFGmCO+*GYkeMx@c?<@1hBt`uCru={dthqO{A6{E< zooU~Rbbn_ZvB=9eSVh&2CCb$>U*wkdlMdlFnO&U!yizia$v~MWAgNx~yw|9}sO-x? zX?Pr%^7>WHHk1{)9_~B1DPvsb_#-)+KkQ`K;pLhb zx-r`^FIQ8;U;8>!x9X7&%NlH87Gz5HrL<)Pk>iA#b{Z<;#t-s zXL1RO_$@(6gSL^HMus%1m7E@0g05E3?x=Bl^zSRYytfav&zCZ>5((no>;ra zMuaP1`KE@83UhaeTxSzL)6zRJ(U2nd%J1erXUldAebBMpUdGAnG{Yxv@$-5$-D9?> zg01XW-8jwhnhLD@6Xdd{t@N3Ij!8?G^KLjMvHVu+Ep(xC!C2@~!BK9CiY7XM-RNX1 zFUpyTYH5P`Ld`uNEcUI=MH_4*svbmTMP~QuwkUIl`bOjxznF`5)}vZ)S;Ciu^S$9; zKed0!(xqH4+R|!Mxzi-~sIW9ue|V(J{?H(F0cGrbX9;%GuXMQfLSxp5mUBmmoC)aA z?9u3X=4w3KrBYZuD?rS((@rxN9fanYy)>I0w8_poc&+n^-Km9+pPXLCXD|pQ3xVEJ z=3(LH`5jlYuGMPzuSZ45a4B?h?PKeIoAzWnDghqUIXKBj@)|19R5?^t3}k=VV&rQ6 zvHhs4v{=%YQOi)7n<};ko!>h!0cG9P6|Xi)pS!mAb%j8#z$R_KykNnQ%Qkis+YO%a zZp>U>Ydop*!w2siZTWHLsX3`4BVW9HF#;Wtr{t_0dr15ctaO`gn@8Lm;gt52N2xPP zDDT9ScS57$dE$?j`p4X7@69(e{ixn|vWHRSh6dWyT5R5jT)S3>h92Foxn`=^RL74_ zFJ2WhnJIS0dmj)_LL0Kc(2zavfP$s9El=wn_jsL)pSQc~8m)0ahg6e8A{FzJ=ZlEV zvAjJ=pt0ixqmsH3DwJ6rynC=)v^OrqGvX%C+Nk?Zl-2i4oU=uce&sKqO|pn!Y!j)o zSWJ;#zU@wLLPzlD7ZvlYKR-yZj1Jlz!~c-r zH%u+gi1ew8=)8uie4a)Mpwk||Z{yOX)5d}FK8tCcdP)bIMffCCwbr8X;;!SnbK|b- z+pIEM=J>dMCfi-+6yt|7y0s-T+tl>lX8Ul~J_(tu?!;IjqqA=IK5ylulUe09oe8Dr z8=akTHoQ@0@kKjCVahW|ljm*^PByrI|Q!{*hx_mq@?)HB7X(>z)4`tC}56rhT%b91_H zbDUrGX=Pl!sq5UcET|eWnq3Li#xB0F?d00_)u@QDs()|2hNhZjbMHAzCHB$^gUBfU z<{R;mS8G=XO$e}&8&LF%j6Y;1B2+(Cd!e?<@=OyXYfqMa%sw?W(IywEV~@^veG%T< zv462qx~I47&O{lj3pRE(UZtlmq6+U*hufKtvqnusWv%6Ge&|GxBK5SesqOMZ6N<+v zj zp7M;?CL8g_a3ixQ{|rOR+Z>v6Mj%qlZ()1>pC%304xft~4 zYxTL*J&!GofA?lj_FW4Blz@`guRYeYedU}SExP%H`udy;cQsd|&8o-4Ww+%GTB$zc z+MS+RDeo8*YiBUJHMJ$1p`-@6_*J}2zt*@qLh_7T#xoyi8CK4vn!b17H z|1&x`mi0AeCu|L)d@N6lVV}Q5>`He3x@~?7(1E2_r`Z}+23GUV$pR&F+l*t8iz5~0 z#msf75bj@bbkR)?)Ovt4wC&UnZ_dy!MnxiZRNhv(+Y|UW@UcNm3Mvbjd(3pXQ8=fl z!dgf5pow=~Moe46+AH(hKkPal7aNngQfRY~Oi|%Z5x%YUT}EZqGwv(ywn4|j%wN3xB~tj->v{kA#coNIF0zE^&Hs7lr( zZnk#zo4pMiq|w$dR6+TZeV~(Wo|=>HZH^SGG(zye{jA$?J>+0aSaJ9vQtWJx))4ul$v_<&T_P%GeG2 zW}(Wk}i$_RMg7U0d)N#%(c|_MrUA^o5hJv29i!PeVXoaQ>D23l-e`lzDmc zf(64hM8vgg=WjEc^K{9W?W^x@H*xl8H@?+cpi(61em3Tke)-$csOB;?GTQp`_~^!H z!2>74$_#io>y6BtRz#_1HHx{aCYT`S8$Sz*m%8Pxd{-_$QQrQwm_nCQj+f8N^oMs3 z-7MnBto{CvFP5t})o^#p{^WMh_WQ*yeYIBLb z7JEl;vzA<97mq5jvgK#YZDzQ6%l+k@KIP<*TV4pAT)JHBTEq^P5$N*pP|=$cGJfOx zjg@*L=WQ@n?sN26JKFq~f+B;mbEjK#@I*GRnsw)$omVA=yiq@TBGvh6$_`Jr3gyn~UP{NII!XrD>uleSPpk{~ZQIxPIi7vBNbk}%XG&LV z)1ln`jmmp%s$=fRIqrrXQyoSNm)?i8Fgb#=x2Rb?w zJDwSd4*V-^d0{9#YVTd@NvX@pc5P;@|B@7~{e>sngT-JK zlBMao&xFgX*JFmqIF+tmv~9{QFRbG$Lb6KC72H!b^-kiom#b@NjfTj?3gE7_rH zlUY;6H{a}fS*tkBV)XjMii>J()}zZ|SZbo8CDJJ{KOfnbmz%OLWcglEmq_1}PtV96 zi|Iq9x9i2CES9&PO%B~1A)#~8(P4$=>^v2hW1GVEp1G8_s34G8^Iaz2>leQD{OsGF zHlm}imydhZc0;#f3H#`4b%IQ`Q_JF#RQ=ZV?YTN_+lad-d4hH=J-M>l!^14^JLzxGFJ#P|*~|ISNhTUmtg)PEaXG zCs#i#cXI*d;H)WCN2)s`1oJ~b41?%4i-N&D|I&r!ACrN7`d-8&d)*0y%c|TW4KMaqv_6 z)Msowy(^9x9mH1&9%40q%=EH}ZDM$l`D8Ki#JBuXVE(FDlFTpoK`sW-+`r7*FukND z-0bxwhNuD1!RST9-8aw^4EP*HUw+eXtjH6xW>^_kH*RinhsAGL;UW#r5)LgIV- z`s?SZYMX6a-{KOHnzO|;uUa6p8nqxZH?a8U7|X@4l$Alv9R*iI3iLImqKl8I`c>tq zf>N;cs6Y%KTb#<5>9scMm$A@@1$O_#qBk7NG;}#Y{hS#Wl@V$zz z8^)nU%GS;Dx866aevbN$(z)>^+1uFJdll5xSskQ!Q&4lft8*9XaDfV~ldZG3XL#_S z>`@?gJc1)-pt6Qtfi)sL{{OjyaKc9W{k5@PtnY;{#ZI%yy!et4?1Qf9=PMAV>?bD*%TRg!Qsel_obz(ag5-Q2 z?Pq10M=}4Du$=W_nqn94hm6Q&*XrE;Kh;kUdWEW!$B&qCG%C%tF)o7rA~Gu@hhc6T z$L8$Z8u>?)%~4g8QI9986FSA+<0!%-^{BPw{l?>0OJ7Fzrun+Bu=&&RR!li^jJrz-`gSfdfWMzMN9lvTl-As zLzf((Pqn@4j>Sm?-c(P|UC~llmK1&H=2pt_c(y%4ZN`gh_nM=Y3{;?9bwGo{>s8$p8^+ow9G)V@D6UpAJyHl= zR;i=w|Lt*tDvwuC7O*TlxuANKZi?StIrSPlr^k2No}a9&tVsFz#4}yVFv#22zk>UC z)RU`vJ*e*nUr=?bw`incaCe4UsAB%Og`GEcX}JUya<7e84n>RB?s#EX8(S&3>zJE*OJ;maNL#$+JocAGd8}^DMlX|7 zuW0F46xFzEB3Erq*IC%{M8u4Zt81IsC-j9$xCJ#kBt}anie2BRZxX7g zcnTeOk}_RHLD_&h8Ijc(Oy>M`5-33(&Rjm=VkimW;bRLOUxcsc&iControls/MenuItemDelegate.qml Controls/ScrollIndicator.qml Controls/OutlineImageButton.qml + Controls/ImageButton.qml