From c750f30f8f68727b4cf09d43758f61034e1263c8 Mon Sep 17 00:00:00 2001 From: Carlos Diaz Date: Sat, 30 Dec 2023 03:40:46 -0600 Subject: [PATCH] test(scale): add tests for scale widget (#4678) --- src/widgets/scale/lv_scale.c | 144 ++++++--- src/widgets/scale/lv_scale.h | 72 ++++- tests/ref_imgs/scale_1.png | Bin 0 -> 3442 bytes tests/ref_imgs/scale_2.png | Bin 0 -> 6073 bytes tests/ref_imgs/scale_3.png | Bin 0 -> 8122 bytes tests/ref_imgs/scale_4.png | Bin 0 -> 10157 bytes tests/src/test_cases/widgets/test_scale.c | 367 ++++++++++++++++++++++ 7 files changed, 527 insertions(+), 56 deletions(-) create mode 100644 tests/ref_imgs/scale_1.png create mode 100644 tests/ref_imgs/scale_2.png create mode 100644 tests/ref_imgs/scale_3.png create mode 100644 tests/ref_imgs/scale_4.png create mode 100644 tests/src/test_cases/widgets/test_scale.c diff --git a/src/widgets/scale/lv_scale.c b/src/widgets/scale/lv_scale.c index f101e6892..c67dc757e 100644 --- a/src/widgets/scale/lv_scale.c +++ b/src/widgets/scale/lv_scale.c @@ -110,7 +110,7 @@ void lv_scale_set_mode(lv_obj_t * obj, lv_scale_mode_t mode) lv_obj_invalidate(obj); } -void lv_scale_set_total_tick_count(lv_obj_t * obj, int32_t total_tick_count) +void lv_scale_set_total_tick_count(lv_obj_t * obj, uint32_t total_tick_count) { LV_ASSERT_OBJ(obj, MY_CLASS); lv_scale_t * scale = (lv_scale_t *)obj; @@ -120,7 +120,7 @@ void lv_scale_set_total_tick_count(lv_obj_t * obj, int32_t total_tick_count) lv_obj_invalidate(obj); } -void lv_scale_set_major_tick_every(lv_obj_t * obj, int32_t major_tick_every) +void lv_scale_set_major_tick_every(lv_obj_t * obj, uint32_t major_tick_every) { LV_ASSERT_OBJ(obj, MY_CLASS); lv_scale_t * scale = (lv_scale_t *)obj; @@ -130,7 +130,7 @@ void lv_scale_set_major_tick_every(lv_obj_t * obj, int32_t major_tick_every) lv_obj_invalidate(obj); } -void lv_scale_set_major_tick_length(lv_obj_t * obj, int32_t major_len) +void lv_scale_set_major_tick_length(lv_obj_t * obj, uint32_t major_len) { LV_ASSERT_OBJ(obj, MY_CLASS); lv_scale_t * scale = (lv_scale_t *)obj; @@ -140,7 +140,7 @@ void lv_scale_set_major_tick_length(lv_obj_t * obj, int32_t major_len) lv_obj_invalidate(obj); } -void lv_scale_set_minor_tick_length(lv_obj_t * obj, int32_t minor_len) +void lv_scale_set_minor_tick_length(lv_obj_t * obj, uint32_t minor_len) { LV_ASSERT_OBJ(obj, MY_CLASS); lv_scale_t * scale = (lv_scale_t *)obj; @@ -357,6 +357,48 @@ void lv_scale_section_set_style(lv_scale_section_t * section, uint32_t part, lv_ * Getter functions *====================*/ +lv_scale_mode_t lv_scale_get_mode(lv_obj_t * obj) +{ + lv_scale_t * scale = (lv_scale_t *)obj; + return scale->mode; +} + +int32_t lv_scale_get_total_tick_count(lv_obj_t * obj) +{ + lv_scale_t * scale = (lv_scale_t *)obj; + return scale->total_tick_count; +} + +int32_t lv_scale_get_major_tick_every(lv_obj_t * obj) +{ + lv_scale_t * scale = (lv_scale_t *)obj; + return scale->major_tick_every; +} + +bool lv_scale_get_label_show(lv_obj_t * obj) +{ + lv_scale_t * scale = (lv_scale_t *)obj; + return scale->label_enabled; +} + +uint32_t lv_scale_get_angle_range(lv_obj_t * obj) +{ + lv_scale_t * scale = (lv_scale_t *)obj; + return scale->angle_range; +} + +int32_t lv_scale_get_range_min_value(lv_obj_t * obj) +{ + lv_scale_t * scale = (lv_scale_t *)obj; + return scale->range_min; +} + +int32_t lv_scale_get_range_max_value(lv_obj_t * obj) +{ + lv_scale_t * scale = (lv_scale_t *)obj; + return scale->range_max; +} + /*===================== * Other functions *====================*/ @@ -907,37 +949,43 @@ static void scale_get_tick_points(lv_obj_t * obj, const uint32_t tick_idx, bool x_ofs = obj->coords.x1 + (pad_right + tick_pad_right); y_ofs = obj->coords.y1 + (main_line_dsc.width / 2U) + pad_top; } - else if(LV_SCALE_MODE_HORIZONTAL_TOP == scale->mode) { + /* LV_SCALE_MODE_HORIZONTAL_TOP == scale->mode */ + else { x_ofs = obj->coords.x1 + (pad_left + tick_pad_left); y_ofs = obj->coords.y2 + (main_line_dsc.width / 2U) - pad_bottom; } - else { /* Nothing to do */ } - if(is_major_tick && ((LV_SCALE_MODE_HORIZONTAL_TOP == scale->mode) || (LV_SCALE_MODE_VERTICAL_RIGHT == scale->mode))) { - major_len *= -1; - } - /* Draw tick lines to the right or top */ - else if(!is_major_tick && (LV_SCALE_MODE_VERTICAL_RIGHT == scale->mode || - LV_SCALE_MODE_HORIZONTAL_TOP == scale->mode)) { - minor_len *= -1; + /* Adjust length when tick will be drawn on horizontal top or vertical right scales */ + if((LV_SCALE_MODE_HORIZONTAL_TOP == scale->mode) || (LV_SCALE_MODE_VERTICAL_RIGHT == scale->mode)) { + if(is_major_tick) { + major_len *= -1; + } + else { + minor_len *= -1; + } } else { /* Nothing to do */ } const int32_t tick_length = is_major_tick ? major_len : minor_len; + /* NOTE + * Minus 1 because tick count starts at 0 + * TODO + * What if total_tick_count is 1? This will lead to an division by 0 further down */ + const uint32_t tmp_tick_count = scale->total_tick_count - 1U; - /* Setup the tick points */ + /* Calculate the position of the tick points based on the mode and tick index */ if(LV_SCALE_MODE_VERTICAL_LEFT == scale->mode || LV_SCALE_MODE_VERTICAL_RIGHT == scale->mode) { /* Vertical position starts at y2 of the scale main line, we start at y2 because the ticks are drawn from bottom to top */ int32_t vertical_position = obj->coords.y2 - (pad_bottom + tick_pad_bottom); - if((scale->total_tick_count - 1U) == tick_idx) { + /* Position the last tick */ + if(tmp_tick_count == tick_idx) { vertical_position = y_ofs; } - /* Increment the tick offset depending of its index */ + /* Otherwise adjust the tick position depending of its index and number of ticks on the scale */ else if(0 != tick_idx) { const int32_t scale_total_height = lv_obj_get_height(obj) - (pad_top + pad_bottom + tick_pad_top + tick_pad_bottom); - int32_t offset = ((int32_t) tick_idx * (int32_t) scale_total_height) / (int32_t)( - scale->total_tick_count - 1); + const int32_t offset = ((int32_t) tick_idx * (int32_t) scale_total_height) / (int32_t)(tmp_tick_count); vertical_position -= offset; } else { /* Nothing to do */ } @@ -950,15 +998,15 @@ static void scale_get_tick_points(lv_obj_t * obj, const uint32_t tick_idx, bool else { /* Horizontal position starts at x1 of the scale main line */ int32_t horizontal_position = x_ofs; - /* Position the last tick at the x2 scale coordinate */ - if((scale->total_tick_count - 1U) == tick_idx) { + + /* Position the last tick */ + if(tmp_tick_count == tick_idx) { horizontal_position = obj->coords.x2 - (pad_left + tick_pad_left); } - /* Increment the tick offset depending of its index */ + /* Otherwise adjust the tick position depending of its index and number of ticks on the scale */ else if(0U != tick_idx) { const int32_t scale_total_width = lv_obj_get_width(obj) - (pad_right + pad_left + tick_pad_right + tick_pad_left); - int32_t offset = ((int32_t) tick_idx * (int32_t) scale_total_width) / (int32_t)( - scale->total_tick_count - 1); + const int32_t offset = ((int32_t) tick_idx * (int32_t) scale_total_width) / (int32_t)(tmp_tick_count); horizontal_position += offset; } else { /* Nothing to do */ } @@ -991,11 +1039,11 @@ static void scale_get_tick_points(lv_obj_t * obj, const uint32_t tick_idx, bool point_closer_to_arc = radius_edge - main_line_dsc.width; adjusted_radio_with_tick_len = point_closer_to_arc - (is_major_tick ? major_len : minor_len); } - else if(LV_SCALE_MODE_ROUND_OUTER == scale->mode) { + /* LV_SCALE_MODE_ROUND_OUTER == scale->mode */ + else { point_closer_to_arc = radius_edge - main_line_dsc.width; adjusted_radio_with_tick_len = point_closer_to_arc + (is_major_tick ? major_len : minor_len); } - else { /* Nothing to do */ } tick_point_a->x = center_point.x + point_closer_to_arc; tick_point_a->y = center_point.y; @@ -1271,32 +1319,38 @@ static void scale_store_main_line_tick_width_compensation(lv_obj_t * obj, const const bool is_major_tick, const int32_t major_tick_width, const int32_t minor_tick_width) { lv_scale_t * scale = (lv_scale_t *)obj; + const bool is_first_tick = 0U == tick_idx; + const bool is_last_tick = scale->total_tick_count == tick_idx; + const int32_t tick_width = is_major_tick ? major_tick_width : minor_tick_width; - if(scale->total_tick_count == tick_idx) { - if((LV_SCALE_MODE_VERTICAL_LEFT == scale->mode) || (LV_SCALE_MODE_VERTICAL_RIGHT == scale->mode)) { - scale->last_tick_width = is_major_tick ? major_tick_width : minor_tick_width; - } - else if((LV_SCALE_MODE_HORIZONTAL_BOTTOM == scale->mode) || (LV_SCALE_MODE_HORIZONTAL_TOP == scale->mode)) { - scale->first_tick_width = is_major_tick ? major_tick_width : minor_tick_width; - } - else if((LV_SCALE_MODE_ROUND_INNER == scale->mode) || (LV_SCALE_MODE_ROUND_OUTER == scale->mode)) { - /* TODO */ - } - else { /* Nothing to do */ } + /* Exit early if tick_idx is not the first nor last tick on the main line */ + if(((!is_last_tick) && (!is_first_tick)) + /* Exit early if scale mode is round. It doesn't support main line compensation */ + || ((LV_SCALE_MODE_ROUND_INNER == scale->mode) || (LV_SCALE_MODE_ROUND_OUTER == scale->mode))) { + return; } - else if(0U == tick_idx) { + + if(is_last_tick) { + /* Mode is vertical */ if((LV_SCALE_MODE_VERTICAL_LEFT == scale->mode) || (LV_SCALE_MODE_VERTICAL_RIGHT == scale->mode)) { - scale->first_tick_width = is_major_tick ? major_tick_width : minor_tick_width; + scale->last_tick_width = tick_width; } - else if((LV_SCALE_MODE_HORIZONTAL_BOTTOM == scale->mode) || (LV_SCALE_MODE_HORIZONTAL_TOP == scale->mode)) { - scale->last_tick_width = is_major_tick ? major_tick_width : minor_tick_width; + /* Mode is horizontal */ + else { + scale->first_tick_width = tick_width; + } + } + /* is_first_tick */ + else { + /* Mode is vertical */ + if((LV_SCALE_MODE_VERTICAL_LEFT == scale->mode) || (LV_SCALE_MODE_VERTICAL_RIGHT == scale->mode)) { + scale->first_tick_width = tick_width; + } + /* Mode is horizontal */ + else { + scale->last_tick_width = tick_width; } - else if((LV_SCALE_MODE_ROUND_INNER == scale->mode) || (LV_SCALE_MODE_ROUND_OUTER == scale->mode)) { - /* TODO */ - } - else { /* Nothing to do */ } } - else { /* Nothing to do */ } } /** diff --git a/src/widgets/scale/lv_scale.h b/src/widgets/scale/lv_scale.h index 5004e9eca..34813b03b 100644 --- a/src/widgets/scale/lv_scale.h +++ b/src/widgets/scale/lv_scale.h @@ -75,21 +75,22 @@ typedef struct { lv_obj_t obj; lv_ll_t section_ll; /**< Linked list for the sections (stores lv_scale_section_t)*/ const char ** txt_src; - int32_t custom_label_cnt; - int32_t major_len; - int32_t minor_len; + lv_scale_mode_t mode; + uint32_t major_len; + uint32_t minor_len; int32_t range_min; int32_t range_max; uint32_t total_tick_count : 15; uint32_t major_tick_every : 15; - lv_scale_mode_t mode; uint32_t label_enabled : 1; - uint32_t post_draw : 1; - int32_t last_tick_width; - int32_t first_tick_width; + uint32_t post_draw : 1; /* Round scale */ uint32_t angle_range; int32_t rotation; + /* Private properties */ + int32_t custom_label_cnt; + int32_t last_tick_width; + int32_t first_tick_width; } lv_scale_t; LV_ATTRIBUTE_EXTERN_DATA extern const lv_obj_class_t lv_scale_class; @@ -125,14 +126,14 @@ void lv_scale_set_mode(lv_obj_t * obj, lv_scale_mode_t mode); * @param obj pointer the scale object * @param total_tick_count New total tick count */ -void lv_scale_set_total_tick_count(lv_obj_t * obj, int32_t total_tick_count); +void lv_scale_set_total_tick_count(lv_obj_t * obj, uint32_t total_tick_count); /** * Sets how often the major tick will be drawn * @param obj pointer the scale object * @param major_tick_every the new count for major tick drawing */ -void lv_scale_set_major_tick_every(lv_obj_t * obj, int32_t major_tick_every); +void lv_scale_set_major_tick_every(lv_obj_t * obj, uint32_t major_tick_every); /** * Sets label visibility @@ -146,14 +147,14 @@ void lv_scale_set_label_show(lv_obj_t * obj, bool show_label); * @param obj pointer the scale object * @param major_len major tick length */ -void lv_scale_set_major_tick_length(lv_obj_t * obj, int32_t major_len); +void lv_scale_set_major_tick_length(lv_obj_t * obj, uint32_t major_len); /** * Sets major tick length * @param obj pointer the scale object * @param minor_len minor tick length */ -void lv_scale_set_minor_tick_length(lv_obj_t * obj, int32_t minor_len); +void lv_scale_set_minor_tick_length(lv_obj_t * obj, uint32_t minor_len); /** * Set the minimal and maximal values on a scale @@ -239,6 +240,55 @@ void lv_scale_section_set_style(lv_scale_section_t * section, uint32_t part, lv_ * Getter functions *====================*/ +/** + * Get scale mode. See @ref lv_scale_mode_t + * @param obj pointer the scale object + * @return Scale mode + */ +lv_scale_mode_t lv_scale_get_mode(lv_obj_t * obj); + +/** + * Get scale total tick count (including minor and major ticks) + * @param obj pointer the scale object + * @return Scale total tick count + */ +int32_t lv_scale_get_total_tick_count(lv_obj_t * obj); + +/** + * Gets how often the major tick will be drawn + * @param obj pointer the scale object + * @return Scale major tick every count + */ +int32_t lv_scale_get_major_tick_every(lv_obj_t * obj); + +/** + * Gets label visibility + * @param obj pointer the scale object + * @return true if tick label is enabled, false otherwise + */ +bool lv_scale_get_label_show(lv_obj_t * obj); + +/** + * Get angle range of a round scale + * @param obj pointer to a scale object + * @return Scale angle_range + */ +uint32_t lv_scale_get_angle_range(lv_obj_t * obj); + +/** + * Get the min range for the given scale section + * @param obj pointer to a scale section object + * @return section minor range + */ +int32_t lv_scale_get_range_min_value(lv_obj_t * obj); + +/** + * Get the max range for the given scale section + * @param obj pointer to a scale section object + * @return section max range + */ +int32_t lv_scale_get_range_max_value(lv_obj_t * obj); + /********************** * MACROS **********************/ diff --git a/tests/ref_imgs/scale_1.png b/tests/ref_imgs/scale_1.png new file mode 100644 index 0000000000000000000000000000000000000000..b35769b4af637cd5de0cc7ac827b03f43272d8ef GIT binary patch literal 3442 zcmeHKYg1EK6g@ymR21uDr~@%Dor+UMqhJ9g5N)w&tI?_@h?pRv&{0S#2th~ykqM4q z20MT#5-X#r!(f1jyg~v}V4yr2A5ahyLKs6pLLN6Hxgn&7)_>41{lNaT&zXDAS$nOu z&$;*JadOz2Rqm?*0Ba5(3OxaUlNSIE<0~BD%&QCKkKp{?!=VRGG0SGhj*;)1MzYTF zD*SS5L5XuH6&%Kn=k2T>fe)a?7I`X;#+h9QJEJHIx0xST@i@|eF0A&31i5u8|B-#P|zML$Ru4~Xu zwf4NvdnBsO5#+7(0aEUD{cE$ODWm6V*e=!AsX zsi}B$yrZK-p->o910}`9>;+v{x=GV#%(utW*D^9Q?FfRPlGD#kUzLtYrBbz8%_LGI zBb(quGMTKWrw0-ED12lA`@d&az-Vu855SA=-7z*xv6y&U;)SFnL;r&yEEY@Q`n*9| z-PuO1qP;DeVVjwpn23psi%UpIpwsC@yeA%iyGv=a*%~+Nc(bz(Wba?<>gqh%=5uTg zO2pxC3;I6@a+YcAK~QR^Qm5?VrlzEn6VrUFZwM`|V27f>-``&|+URF9#QF}8jg5_r z++)YvF6y!!A|=DYOl@>pnx&vA@;9SZPrs6>9dC>EEv04+kB;J8ITVVpzrQ~vHT4*o z>}L3kH86#O`uh5z!9hHcxohql7G@`bAlP|2I88sv=dn?nNlpn5_fh2kQ}}k1X6*hE z@7MmD&3I-yef6Jc-xLgpQDU2a_m-lI)(z6LHEq{)6CF&Vc#*{I>+5^`_%Z(&uU~hH zS5iWt!Q}_68+n`=dH6GY%?)9?3Sf)1{_`f2-K^w>aq&k<&)N0HPJkVx`@dE>c!p#& z)Ye`}OWVt0I1JIV9WN2_YcV+r2GiCikSwAWgDPpCRHcGSb2uF6#69kiW?EEKuQim% zQ7V<{ldFrhbB*rft?T=tKjZj(K8W3pyUg$J z)8P=~(#WRigCi!%WHKgaiN_<}U0q#4K|yfmnd#|i(SVWrqNSxpBwBEm$7v<=olu|r zZ!4q~VO*#k42#8raBxdyR>?VMsZ`B}50_!k=>t|DL;ZeA^%DiLU}`&oCKff77J3}D z%U_7a;>O0kEng}Y7BXZURMjLBm9a}2o3h*2%X;29;UZqXi=ffuGFf@}%Z`p*V{=si z2G$TzD%P9eD@Z1Us?liT_nW&P zYqEDjuh(1WBs3~jjK>x;Qs!_Ex0H2QpTycOB4TJN4`|&73z<1>2SVpw5+7Rqddp#wBNDWEgua-Lbwbzq^$|=Nl5;v6+h&mCb4r zXkKk{aBJ+Fo>WXgd+lAdQw=NAeCq(5NWC~OSb zq1csl1a`(t4@f>9j}Kv?Z>*z>!JeBQLgJ+Hh+%+4PfR3}qL%u_$pwsWIP)+a9z-E) zvOk_CyO$FHmPvs3$}$PdB>ek@@jr1wjotC-kJ~z>Dcif@zZ5upkR1B#>*(u$0}iHC A4gdfE literal 0 HcmV?d00001 diff --git a/tests/ref_imgs/scale_2.png b/tests/ref_imgs/scale_2.png new file mode 100644 index 0000000000000000000000000000000000000000..a82f560a362947652a09212fb3c8591e78c9d8e3 GIT binary patch literal 6073 zcmeHLX;4#XmkuC;(qi-R2?8P-TJU34*#!yRMrj)akr4JpP(+rn6CpwX8;nGtTUtIXV@J`#c60V z3wyi4z3|*gY?KGwuFH_J_juDm>!I^jehqhtP|cJCo7TBwNj_r98;hj1)4LOIjOM4? zuFi)?a&y8D^Qo~+6o1|mRzd-Rj@Wj?bU~o$DAfuO=>8rpQ2bRv`A8kTerYfC$3%T$M{M z-2hZmeAY6pX+$vb!h^Igwti;WK{L~qsS!VsK@?RZWw)4zuzBJ3O)1$<`e@oX8UKjx z0IE5Ps))aJC+$!Gzdi*sw32Tc@nIBJE?T>9kJgc~{v@eh?$uSr6{a8x2;_x_cu0ec zlf_FQB5US9%pa#uCML|OSHxdc!QQz&6T2w)l|Cdv{F{J0Sp~j@N`tsqudSB|=o?ul z5a`V=IgrJv1aZ($)_*Sf+Au{yYTe4Q5_5tDCdPPUG_zY-vna-ds1L4)FIs&g*rq77 zwaJ`p#BB_tw}0SyH~r+RVj6R;FDG2dFjp{GL?GL)!YA*wCPX(NzcS;v*_G*xv2qix z=PRKCmTjYYc4nK$I@8_?>VWr2YzZ|UTGCowFAvS%dEp@z;u>8p*w((qxQw!7rnj8f zK(=UK?^Wc_K<;8p+0t5*tijjn=F!Vjf%PwIgZODGHH`v(635(h&DFME%G*xBQ-T@j+*T*hY#lY0aM3@o3sAK&vRoJBsYV0tkE zH~|jV5F?u#ZK<1TKqmCIdTE%09+c?y7uSI+e$c>C=&*8;+A{7^)SLOKjH%PCrJxx>&Pn4F!CPs7 z_%v~pV-wAKtBc`2!M(gUX(laHBhKi+r<5VuimY-2)RrJ3Cw}k}>GcL6*Gmsp;DoQC zdAjnZBT(o`SXs?LYO^uJPe5`A02h|R&W}6xEJ7};cvrXRK##boRuS)O7Z9YZq&qo)^y*k$t=p%h>OJ?O60c4F&wVsQw0IK@fVI#G(9~7H! zvJGr`b4xU!7FE*D>)Gy;uBo;OA!}NV;{rx%fVgBr=Lp&|cCeBMo=Xs3mTelvA-7&s z@vN7~#p@l@#GJFS>iIFgo8))z#DlpZtxAx47;N{%l%NJ3%F|~6^4XR^SEey7+ev+t zRF_jk2S9HT%j^M$DpoGtVR)sv-T>^9e8(j@8Hj=qF<~{M!WGd!3Sij#D?cKbo5=!d zrw+2T7NGHWj!2$-VtCvRtKCS!nI zZWbt;-Tjwld(X(&xi>5qZJBUJGXdN;-UQ#`amORG=wfAOB{W*bS03&z5CLujb=g{0 zFwK3kyoKh=%bc9g;w-a2_6b?B(@>7k)_jmn!pP>f9e4r$3)S`r2VW1D0DcbISI%+Lzli5u^D*sI=n8_p)QnJ#nTUI zrc&fxji5~Lov>g+WSd0rl9^L;Y@=~ZH3rjHJ$^fv;QNRxs+J`@tN*nhDAgX?o*+A` z*`%EtRHa_uWltI9Bf7n+SXZ|kejY}v!F5wH&wF%E%l%?$V;PHKzF!88`gNY>Lhd>R zX4`ZC8_`UQvIch_k&N$!x~*_2^>Fckc(6G&Sm1+t=+iMxvEMGCf~z)5OHTP`D+ej&C3OA);3d>h*Hxz|nrK-6aF38RJ;7 z`EL8Oq-_Emi309BhFr&`SM_Sgz&o%FGqW`Ba7P6h3U-!1qrHF1@ehR&0l0FCJsYl2 z8`*v)W|sreHNf4VP)_3dJ5HBDcS@^2wEcdUgNn!HyZp)#+LMN+1w?s^7Wh2m5*i`d z?*&vfIZuwaHaPjb_rm4H)3C-&e zU-Wkd30;eYjJ$CcG9_WdadPMN$GhPDN0RmcJED|RXl(?n}F?{!}b3vR44~Il;b?xmjA*X-M?@L*P~O7z~o8< zG~K~AwJ0<#WsOh{>F>2ROc2+{S}yAzu7iN->8p7E0^kILZgXx~_IPdmXf0c=MJgoV zllpuU#r6C8G(8WZnnF8ti$acpGP*t)8*n${BKRNL`b!jl_Jcj;k_jE5(sCy*J`_5n zUaP2L=kVS?wDpA_G9g`zg;uxcex%^*nOGd$=H$Q@R$x;JAu%Dkr;yr6;5rpPaBUi*FvSKZ=v@&O%|!l<@WQ6P?(n z*Rhy|z>+hg3IUmKbiD@mXchd6yNb>N@*1hx%@Ut|;lF4GYosbMVTy_hK~F>RDbM(N zgqRyp=(VT;LV;P>Y5Lhxd1U86rS_Svw=wWcnE~V}kB>n+B@+IVONvqt-GnBbPW3da z#g(R)g1!5cXCaHhFFZ;eeC2ObyqSYxiEr#XUyY)S4e@Do(n~?$+J{06W`*B)s8mij z5LeZ!%zMwaCH*{Pu>b3X-CsLdR30oT3BZ2^e;WA~>KT~rx>>5_NmZ61&EZcKKKIbt zQVEVOWZbUD**jYJ2<4C$x0DE!f-dn`9jl{wxV7FTdLvz_&gF z9~^^%-hEJJ2EE6aU*~~Dneki}A#zHNwh{Sh;xaN4w&y3PuAbUN?eP1i{aOG5g3Y1A zNVrKe9w#3>cvS^=s$rqNxSE&iJ?d()J&4H5Q1^}^U%%Qd%RBNA&`C|P)}dc+Cu%G z0~u{%e#_gWx}sh-V2)3j!xvBM$2cvFG&^|+P;CqV4OgS~zS~u_HWF>>UffnU!(Uoa zT+spu;>rx&dxp-)pE@A3_L@gh-Liz5M9r$wSVA>*|9@3eXAfX7KwZO(QXTM4Fl%NQ z$oxkjU*Dg}OUu)q>(Ma615a~x{_ei42DUdN8Q_-ZvUW9Md%7%FqPr$t-jXaQGcX26 zRp`Cd%J3u(R{%|_oP^CU<(t*N^=afU;L2Q`5$Kv(2~Jml`OxR4l^!=unk4WhGtyM8mFhtl%;G9>)o%P{MYhLpbj`b5* zlgh5<49D2gAOt`wfZw`Xa@h@Cm_LNG-5gemrw<=Ekz`#F-x1BOQaVr08hIc?cos$> z9ec0~664rWkWR8Fca0zn4wPa`TuB-kjLHDj2Xw!>!xc+mvnY8Kb)J z&O{96wt6`mOdLK1MD*PR)jKqxu4Lu5CMPYkP4(Q!diE{Ce-yz!BbM0N_>Sx~?grVG zsTe=eh2nJvFJbd*OSt(Gu6(jr^v53njg|B20XrGbr=m8R=(#~_eJd$~HAFY3ZFWp* z0Ie3qBn7k}cG?@)mv_+p@GS>TgyC_q?c*u_Ri`NdAcQ7_w((XQy;UTUYU4HE`f@8I z)PDgktr+Xa6RUaY7Oy0P6daIwARzM`l=qQlX?p#4F}QMdEOb!+&3Rk=RbU8G9J5EI zw-eE7u^|ggB+hNjU;Jz$p}FF0NU_@XGmnzm{WzZ}dcTSmJzs3PoJV)yJ~5vRMKW@c zHp1aZDp7R3%!R=9S)&FGaHC7$TKU4c$df{sG8*Msn?lwJNv`BZ80%2VmZdbdu@)*45w2_@WZ##;7|YF? zCCkV#_I)4B472&Y-FwdWch2ve-#NekfA_EX%=_~^&--~k?`L~G@3(*4)8*kh#|42v zc=YbvGJ-(Z#2^rsDNa_fGVnMp69N$p(YvMjz$ay$5cKEpv)NwSh*o$@*(UHXi^g^`F`XH6JO)RU6%`tw1ZBar>uJ}@4W7jWO zooopSOdVxuG)p%+O}%erq{O9WFjGyY>HZ0L6EW`A@TcJdgmrz6U;BHugRJs_fe8ZY z#ril}e{q}q_%Wv5n@_w_PG*&mrMu0OODW+Lr;il4#L11g<16bG1-0N<-<=IBw=wcc z{`%O6`^3kBBK0k&ZdK$8;u+4IL47MT<#Q5#Gj6QZjnKs}m4+a;gpAO-e%I$97cC8A z+M6OGY`}(>rBIua%9l%dvo9r%mHH0mSGg!Xov2)?s^TD<_C=>%eY&Kq9nQNxoZEM` z;aqU*Dfms}JbXDRY%v{CyENU_lYcS)=z|o6Qg@wM+|UV?mL`=BszI_TrkWgI<-+@} zX!~AjTXg+eie2lP6N|wuTNYxG&%)?qL$$J*Y{59W?%(xa^NBp4m=bS&owC!IzQbV0 zg``>Uw|L4G`;))LM`bJQt~F=L)!HuYw@y+#SsSE6*l$(1EcSlm7Sw3;_nl1`{`16z z=(2)y=TWu_zyI#CY;1T5k>I`ZV*2a-#P_0`=il6vX61e(rdPlr8hFF^nr&ilYKgF_ z+xLz>QF3qo1;MMOCl%1+1Cj?$%@-o8T_;7fF3Vg&)62W<1)yO~tnc+=P5Gp)_Q+%I zuc1eGi)zXFdTdg?5zJSa9Hv<&E+gY8#Jp5V5sy6g8(`k%eDtL>v?muASBq*Al2qwruv-!QedoS%6OFjiEyo9=j4Q{-SMAd!HE_AL zwR;q75G_vBC9hp{ZM0%hG#&2Ma!k_y;JqrxZqe7C#l;7vynw|F5vaWWBUnJsmPI$$ zxf|z%NK+x^7vy`dEe)V|GSNGth^?u_lG<`-6sh60$X!$&#c4ttR*bh%f84?^m~x;L zxo9H$NfoQV*gLH1b+=(9ABeE1dhCK{W0iTQAKG*uzSs0pd1rBWVdrsn_K1yw?@ab= zY&&Ib)RXyBj!xMOq z1w}G4SCUkGyY}9i<=nW~U;eY1^?gTis%mb4?@A4pLcseo8!9+pq_}TUNPaEHkz#@p zlY&H1HmsT7`;3IQ8Rp=Zyg@+iAQ7si#af)Fnn&x0j)pD*?NPL2KP{zAA9msU^HeIR10fJVldZGsqyT>5lyTUgmC0)ajFIe zL_G-keea-}W!_q6bJHU0Yx~sI=T%knsVW3j7t<0AneEui{X<6twqz9xExqRjvGC+R zib@tO(>K0or+g}7XzuxJ&RzT#bzvYjmK{d@(8kJ4@jk)IVOedzEb*uvx3Kf*_PYmW zLB-nUqkswgDePI4JFDt78k_B8c;vdOS7|V;>}lsfQq9Cktuxmi&Jk+H6L4odMXk0v zW}~qtn4$pl(OeqoU}3}|Whg_Nb2_Jf!{2^%mLt$%Fu z=dQ&gNKZ@?EAwkvaiiysj9|2IJnqaSvw7`Xw@CeqU7Y|w627j;nW@c}hRg2Wel5Z) zeiuUw|77LX71}L?-M_|mG)KXK)2=hF+r5@i{kbd{W|BgN-1IzD)1AObjq$P$gEE<) zoZ=*{(w>@!K~+9hrSBKjdkqf(4O=aT$217;H|y>rKhFswTi0tOuqsjP7sp?*hi>&w zn7C@db_zzaEzRDx)u}9fC>pD-a-VG2I9Bg7_caNDDOOBR_g$6QXlFesdxEuRzH2Ju zxQP17AcHoE9Q+P#S!PI`6WtuFSmZG-ak^A*Ud+$RaT4B-cZJt%#FjnM_ADy6e@d2H z{GyUAQ1TIj3T@dK-vGujwP7|j&t|!!Wn3jS|e+P51{ZIHGL2Y%|V~#wvY&6FY4{N_5=h44-ZctruO+xNl+^JhB;6>%{+c5 z{9s+809VX_N2h@l3AA-0Tt8x+4^6{GI zHg?;yaMJ6H)eXW|jX62>aSBW7Tzm5U4(jOCjU?YegX9Sx21uZW>DMJfx0LKXJ?STOJXpQ33R;%Ew$))1lb~x4Z`GY* zZI3!H?I;uL%?pgy0NL2OyL(V1mp?DRScK@ExZv!9$%0z&QEW9`uI>m;a*A7_hV7Ba z6r_wCmYm-?ohkvbU<2Q~1r)-9e+toG`t8~3`YzXr1JA5gyo)Ew4il_XAFD7QD|fj& zNTGZhbPvwyjbwKNR02+Fe;&5C)%yP1OWaLcO|pzlZK-0qXn##hS>}~<@MpN-Mt*Qe zGhy6=Y3z>AS-ItlpV3zE1(y!DNR$pSkapxbB4D4gUa-)5NuW{$Z1J6J;`!(lx5cp# zcJn8+e014c_M*_Hm(nQz{cT%Xev)3o1@m)a1%L||;gDv~+gKhjTzI_jhLrLtmv7hl za@4u|9~{0cmrMiZD&smcd+wEA?qp?2x>zcfoL~Lanm7=77{su2vb3^4E(T& zRzM82Y&8`zTlAg3W8CYqls-y;7nf1^^!u9+)2WYH%XPtf8LM4jenYeNfJmr!qO?Kz z-J4`i>5k7lt2mR*pZ85IRQx-Xo%&kpg*4_;K;2k5uL_DnB3b&TKQP~MK7wC7bh^6a zQ;mH{>^D~V+qRlV)v+^SQ&iiEMCm`u;WyiWv`_gmmP(Dpy46=bi0APhvqB{(IdS{w zrk9206@ExgJKM>KN{;ER*vG#o9Pc;@zEm8&gapa0sX5Xqe@wbV1gz|=;1!yAO}Rfv z8<*SPOaMYZ0-?zW+0R}m6*C?W9-gV?;g;94)y#Q{ue}=D zUr@hZAaKFF(0)4jI}{#s1+>dt;>Qb1UkyLJ7#S}u9CP*?0Iz>l+U?{(TVQmbW)SYw z-8B-?1_Sfo?sVe&*6c-JP{QI%nPx(`1LxN8{Pp4K)G#*rEV1;yTy-{RJ7_3iJEMJy z*RP*Gefqk_Ue{n>8oik>=LSE={`zL1(v;_-=-wSCJv~>AgUF!$#5fhNyDqmIQqn?d$d|Rzm-9$N$SH zpge0)w~tf2kn&ba#-QB3bDL#UtW-?3E4J@=F;dKjJ5Jg>$#6OR_=$gJ2p#X!)cr!C zIQNN^e{}VuzPr!?4PhVs(HLOPX;;&C+|B7f+mHT0H|TTP0muUUZ0VwYg0yH14~WjJ z27O=73}+hoe=MW=(!@ha$>#_ATmGAP_RIV8`ue|vPD#P7)ma%dWmc}PAS>#fR|R1T zjcR&%cX=42%W3z@pb$j$e>|voA~e0r}fO{dU*RuGv;y?tDn{U=y&9 z1kB0>2rRTEe*`R_>3k@`&0!yYC=$P32?`3>Rbv~i1IIcU7gtv|80Quf?typ~y98om z5Ia+7d#WJpPQn#)c`<$92L{i}mH`Ngm2=tI*NK)%Wk#LN%TPACdepYa5pHh0hsZ{n}-A@4;B4HmB$f;$MAyu%NXt!GKIkyT;WQ9>vvU2SY}MerU{9kQ@VL`SYEIYKzEh$RFp$Rn=DWd0 zj3$P4C{b^7&S|uuS&lmArmXoV`=sEt6Yv;=#p6N( zT5#%6oC4Ykh%E&T7r=nw-U-JJbJpk(sFm7%BEo=V4wkLX&7m*wcxm)iYA?iGqExKn zHo`-b(+(g%ka}N9M8--(WAwmxa{+J@fN6Yn04?A99x_)MUggzOy*>uk&iOI7Djirp zMSucQ?=kch+Jd2&-1eae8;xq>3) z6AX0wYt@}Oa!fMCrRiU$1J6IUGHHTwFSkUe@M)sdo?N%>eBhEa{rkez{?Z&B*wR*`GfIQ^AQ}Wfsml z0n3<3Qh}5xYNm-Jc_~|B%*~*h85`7WE79Be;Zwk5(#y z^s78P*HPejUyG@ct3-kY^@>mlDFV=GFE(-ha?i$^V3L7(+4 z)qDIZ0R0;ncji{J_IpN0_+Lucz`;cI&VaU&E1xE*nk^-COCO z{=$2n*KZ4WPaNggZGR_|;bF>z5)6R3^WMU=pEo0?06q`^u=t!GdCJOl{rqITB+fJN zNmJA7z~3Ca-6-GP2~|F$j}G;-R_b|2np=+88`#*JP)FnO84i3KFvmZ}#Y3JH{8O1d zQXaUD`Vj?X^T6~XU@>i4yH^jDVmdtWwE3ZK1FV^*`ipcGc z&=yk7B;E&p*&z2)qW*?!x{7Z^-Ahv)Tg_GZz?t~Yu$DtiPL>{3ny0SDM!uSrv`HdM zH$9Um{XC3jBzs?Ke+Zy>(8bBk-9k3-L}BcEMz5(ZN^&3w%pTQbzh6J2PXP3gGE_FZ zW?rGiS+QB7_EZf}Ds8NE{xA%2&#c3 zzH`?BXo0rOt(Ksolgpl%^d<)!1c--%lsl_rbxm=pFmS}5s4pHmN-=BHeSw>M9#?{u z4LzJ9Yzl|s@*LaVodr|GFP|raSqcy~qy?Oe9EKjG%L~I2j!6pd@=8GJ>UckJ$LSx^l9!N&U)8J%rHXAN9t(o^Dd|8odK)mU+jIeYpp|lEPey zI1EKyR;qE^CmhfSnz@Q!3n*I?QKTq$0%;&W5o9##kCzZX_hV5kQS+4PTmC2;gMS`gt6 zs(sKN@#W{@_jeyX;#so1UxlHDj{&GS?z`%meqR0}s6-$4zY2?mLK9@nRd#TeTAYbC zK0i7|=`nRG&*e(Ytsgv4^V&|-;`E;I8(WHO|Dt{(Mk9os9{pH9L143l*7B8mK;(-~ zox?`Twi{uHKL!eK66N0*n{-21i?yW}gYk$7qF$BSj8UNasNDU8I!&chSN!;>L?8aMJ9 z%5|}tp5+F7AGkzEJJKa@H7A{LxKV~uw0J3M+Ew%!NDn>4iAT}@Eu@Y2gW+i wLWEBpnDPC4srT>o<-hm2|I5ua`G5rx;bM1@xTgn&jkihzoM(sGn0 zEp#F!L{LGbM0!mkD4irgfIvc$Z{xk^H+R1I?#yq#neV^*2Q$o@{qFtlwVu7!v!1md zU$rvZxm|j@kdV+$^WT5FE+n*BQAkMmyVxf1q{}not&otSkoj*HZv>~&Dc-?}@81^@5*ll<3>6ZJxO=!nNa)aBdm*9c+fE8ad=Zfpy5O-%TuAXuq^J<& z^@mMDe_nAF7Wz&3Kg0fGkpC-$d`yrR`tGk^legy7I%_P}A9lBEyLyW~aAP_4)sa8gnlgwj20WM$oq`bw4+-(O$G9cYzTPm3=ej5JqO+%lxUf3j}M#M^^U zG$q`vpKwnw7HgL?kkId6PkdxMU32*w^unP+SK*f1^ckXW2oKRZYa&)&u)V&lWajC? zOs|w06S2Lmu)XGcTg?iK={i&6;?^h0`U?bn)nTkZr=m+3C-WTwws(+?3AiQpCOZ&g z(I0tKRmQN|V>VxP@`%B{3$j+bH?_Rd3-}5ANPczNYxMDWUa>HLu9}ipS@mT+;Z0?f^flAc-w8pRLI94&9*B?+p)TWm5-w??2X zYx|N7XUZ-YjU&{x>XkeC`YK4gJHpibw79s3#dn1c+@{r$CKxKx>N=Hw1R<_jO!Bj> z&*6(_Lsw)lXY!A!6D>dDc;*hk%STBAmACn?9#-86Hpw$6y*_-d4S#Bf(t4=H>}Im& zaE`Ff;s*Tus4!MJoZZ8qN*U|;cr8E;ZzS@YXsN#EhT@q;1J%HJOT$CA_x+CDh?Z@d z|I1imxCt6gVW1JU?5ZHSfvdEG#<%H1 zqyRW_qPxqS-B+J#lcWSq7X8dbg2Ybg?$>EDD6Hvi#5r;uSFze~L zHk#+`<`no&KL@p3Rkd?DJr6TfgQ9hDvp#yaudKBF_`FtKenQ+va1Q%iX`j`>{XW4F zCNH}?JD)G@_;B^wa1ZehlUcfTm9}Yp_!`b&{XYCmS^eW_*r_~+j7waqInLM|q*!kZauHF44X z^pIdmMjHzazJQgxkDPxp%_!T$&hz3#9+8mw%LM&1K1DJ!dQd;_*14kBdl#kJjyq}O z&c&O`TsXN&JlM%=n3Eq9NL_uC!vND&qkHijgW^+m*>Ao0FJG+LJc++Dt`F-k4Rsm@ z6TL3Abx2}QpKVrqWwR3H?TGL}+hTt16_z~|DJol!BCHhUlZor;#O|*yHb*3iD7*v} zScnJOV#YThobf={Q+(p-c{pS0NYnbq8xqCd;o#%%-yvdxrp+c|ThhE*GwPX!$*5bN z%n%z*N&i(5@K?o{u3f7h*?So!SoxF&F}8q{=>395!Ch06Xr6VLdRV?W9$O z`SJNAv6J7`-qWEwwfSLiC!`;6z@*XDkQRq#WrI$k8ah;+vdZGnFaz6?(=-jIA4gmS zsEW?YE}wi-3;SK|=FW7}^tSODHu;ouSc8WYX>+u(tMJxg>Iky&UH_cT{HMMo3%}tc z$5w~1hM$=}u!6{yZ$p${pZ|jv+VZlkpv*;p6omjEK5Is<@cbNc@X4243U^O+yJQ$scs?SahG%_q=Q}&*doR2V&kjENVV1A@-L=|WLP!?T6yFvC zHRodRBX#A~@CodLW_(HkzRN1zadE&lFuvf}ZRT9adVgw=Hd5JOxBt1a2coib&EC?a ziY25HmSug4G&3^uCZy9=;CLQYzw|LB8aFIwyh<=lFbutH+O=2MObQ_C(k*9a%;>^4 z>%jY5Zf(%|QeMtqr4jwW(+XL$mru(c!nl8sxwlhtK3}2uPF<*j<+tsApUtfEHs&!F zgQ^#-Eu=JMH4Dc{ZZHd-6Iswd_><8Ey}eG0oi-;NYj>@;qAT-A7ci&`{!buc9f6;} zGa+Ao?o--rvA_!!zuqwzRAMz16+jtP(x(=Lw;}pd15(WNMWf!h^}L2ZNUdClp)7P98W> zrOnII(IC|&4TmDbbB7BI-gY(Cp$zqiIz~tkd}ZVdy>ByX`P1_s z)vAY6H6?c_)tic$RhnsQV!T6~#YR)&EmJ^t9u6b)?nE06zSwL0O7rdb45|tXNpdly zFn8?2g5z_F!K=QI z@AZT~CcBztypc@R<6jKWjxCxBspmk(#m@JlTh+d9I?TQexcdb}#uA22a;8hY;F*-LU?HFna`78%pkvwQ@X&E`{de~{)I z^2Kn^wD0wn)d-0BN`AqNMRoFP!TIjb=UbkM^coO~_N&%t`)pU**Pg1qG8SVoK{i^q7L}EjiEaGA znc!(6jXHxH^7fY(!~o)T%3<)CRdy1dm#4*XFLs9Z2MjH}fHb~)Tc>Ml>g_#b6Jswf zRSbjmZ%_-DUQpn>{j3X7!fq}q+E`S5Zs>h!FtrY1>~iN$L)&XP z8LceIQY~1p8#~9+b27szoC*e4X?nZ7UtfIO@8VSD+AzF+|AIh7az`O%HRdr+7Zv8! zasqqRQDg3_LO*c9(-U6{?hK(g6)(c=GA3^9S3}n=l(8)V6>|U^qb791l=gVpYUjFEd@+Q(W zD|)|FZgR3Cw3y-JZDHD5&*6;OSWUyn^IRv|ay(m`&@(m$YHH3h+Bq{bkJK?CFf?z8 z%V;eQU$)rc-eF`!arqFjfw3+G;@k6-9OBVOJRWBL|1n+kFGz1!0(;S)zsb@zJb&65 zdOX@5T4_ny2=!ua3!(|m1K}bsB`4JCT^C`rdR`Nbc~c{|utuMRQ?Xue#EOH=?bNLU7PZoyqy!smVww|?KFDjsmDWw?}EzR?n3X*#( z5bTY_q#J0aK-HVKOFxTT;=)ObUpPtNN7=E*U_k)c<^*diJ_BikL=6wQ)T~V;`_}7X zpBcF~t&-7<Ji)!Cu^L(g2=D)T){T#jhQ)@iKBGW z@>~1cQ*6&*!UrFS=J|mAVQ{ftX%UKgC z+rfMXK+G*i#&P-H@EC0^F=v-Dp~TNPI4OJuBU?wFCznl zNmRFrHZ=t98?f+-MYg8U@93swbf~JS>2l|yQhjIeMeX|3Qn!z8bu#mIgImS7*yZCL zYgS;aQuA!lkJq=VBu%QL$HtddcB-XwlMOrH*W+gg{nlG%i~q#%WL!XG>(Sa-ULbBF zMxu6cJ_NS&?sk9c8Vuz0dJos2d{k5Z>gme_PKbBbBnbQt6za_uF0wjO=Lwk9np&0~ z?M@Aw3LdDwI{o8C9YBxJVg6lFKVquqg+Q!qMseUa4ZEdQRt=}2kj3I_iX4Xo!9p|>kRY8os_A1Bi!Oex=vwT&VpS`+H z)z-3ku3LU0q#wizuUz<;Ug{pB2tg zku>XiE{-`=9&UDR{OO96%~tNcKnog|`XuJmy>Lj`!w(PUDg+mpA=9Kh^jV^aq(^R#m4*1kdMBM?{`Hk>)FKV;c@5M{vwXggO5U+e{;Pkc{yDHgzA&i7lSu{jERXM>z_prQMiJAhlG6gW%7Adw4UH~bApV>fn%G*%Pf6t*iGpa@LT&C@*b<(d1yCYoJh3r zmIogm+$|4vauRdnzZ}cjIvOwSa`R?zm-%#UDpdbDnDzq1{+W-RpZ89sHr!Zldc4=+ zvzrQjOw*3&Y|{Yj-9tvz{-NBB^-G?qs)%k}N0eGh3}5WEA9x15Z?>#tlu zqpjRVzr=?f8El9kSi3g-#V*R_T#6Oz{ro1>1Nigc%~8YO_j#-zb8~55K^NFPf;_+} zV++B-_xGj@6Uq-G;bsFRdRRzCfn|~F{v-O5(SNyemX_#GR+5c_&BT-{2nx4-6f+1HDV?G4PNbTNH$!+r;e?U07jWLIo2NgN<#@}rc=hvDfzw@8pJUiQ!%5$NzpyWSpX3%o z9}z96;PeWv&$P?Q8xQo>2;?5eeHxuUn1QP~eRpoaLq(Ws2xfL_`7>>sDbe$AZGxW6v*F2v`cDm& z9Yv#PtB*hCShlJi``uq&Q8H3=@bL0kavm=1)>WNveM$8OVe6}7F{dv3A5c6bRc*5J zPTJrQ_9_7d>w4(_u13qxigxN<{uTc{il@Mu|sr=ti$MT-lL}v!Z2aVXXf4N%o@AAtL%}+o!1pJBtP>3Z#}m~JfAPLebtIR>?D=~TQ0wDk?d<%3^=k!r z%_6jg_A>9sk2kJMQ;p9QNaXTiH&x{pBkgU7JkIw&*74Nu|L!+3Mq%DU-ugR)&b# zrVU4=gA^vNv{8%$St1KQDsf)hkI}KZt#NP?myn9X0d{tnx)2AJ_%#~1fo4>D4m+Qq zlKLX!kmiuA2dfwMR%N`|T&NaA29KHk&+i+PfQ;$SXJu|ubtKcS!gi3AygupdcQQqSwq88kd;A= z02IsgrJIAr6SFXKT@|`%FgFSkWts94S*H*jHJAKPMfZ8&aIrLhyuJox4j>9S0l|1l z$Dv@9OT*~8!#h(*s|6CgZrak^!1Dy0QCK5ZWb5V)vS`vFSY@ll7y9XrW1kzXkF;gM zTddMkwf%_SA1qM-T+TCd2`j8HMXrvim`TT&xN>gQ=bF)m8Zgcn9L_3O=pBwPh%n{# zC?9YD9|P>cn&CX*fIz9Uuc0mc9*M zW7MW5t6S610V7WXr7*e;ArT_mP9CW7C3;*2ifdE4GZ3OILz6BRPxHAlXS96$P8>jU z=rnT>pB=#Zc#vP6)L%OSO|?5Wh0i}_WD~raf>~>=!SE-SC~ zYqs~va5zvCI5)cE`_CyGbo|v*9TG_ICuM|Wt2R`z%^mTLlaXLG1cs3>; z1HXt8khexX7~N3)_Jj0IpPdy~dMTxluypdF`LK;Oas8pf4Qxr3hnFuca?nT5rGB~Z zS;ARE_u^}=V$%Pl*w0Epg~eSl*$LzVUQic2*iOW)`q&h_OG`B9V-A>?i^zOqoz%}#ttD5 z)b!Z?_Cqk?*TnCI$%R!Et3P&#_zCRwHe+DuEq?6$yV53?(CJm635iS!@A+r<;ldey z|7L`)?nUR%z5cCg>H;pQK=r_wh~xziA8P~Ku*DX0YvCj28*T3i>HppoADr&AY~N6L zKznUSnq;8=r2_9lTx%591_DfT)J}8qCRt^pl^qYsnW-tH$~xcw)~m$+-K(VKWP=9$ z*4rC*0sg=GmgnDg!;=y4^xokx%y>~eP65Hzevv-tm6@w&_la96K?@4;Ue7GOWPE!gO4eA#Hi0vRZO#zY_s%poPM0f} ztxj0E$u3W~X&SedfxY^cdvIy{t+r!amJyR$dKmfX&rWfvCU=X&wb%F(MmJN>(5hSoN@EtI1AmG$m#!yC zto;M%6w{M!pqtAH{^7|?{Wj8Bj>rEPdYk~F0j0Du=MEDzGG(F9aiK$xa$`Z|ov~(2 z&F_)E4oYzO+ujy(d9@QMZM}sL4xaU@=k#ur=qZ{7xaclFdyT+bpcwvPxSR*9j*peG zks!{Gt1}CVap5%dT5oS&Zmh0gJ#MGW8JL)??ahax4_jhlDV4Qs^u(Ndx0z&yz037y z+<~ONwp=;rbJ7%LKR_F4Dybc`#jlHj9`R)4WD2RQqXyy0B(vV@c!B^u-5owMlFCQ@@A03zlc||pl;8eJz?tH(AfrRX=P1IiW2tmc=6+` z$K!|l`sM4~=Ye7+m0_PzU;B5@Hxd`{`!E!yV62lI zNj2w-%NED=m{$}AS@R!>K0et@6ztcuA=%kS;kaLirLyvMGyR3A*;$~MD4s>!{LusM%t5J@&|=1p1vInxlP>TGwZ$KmjP_ zO3R?6W%RUtN0t=?o&LOKP)BFlS(!CA^fzrXtJbF6u27&kl?G1#hy}P1m5FU)H71k7 z{wh-YdMT4PfM}MpY)xA3>E8+z>TA~jbDa9m<(>cBX8O;)t-mj_4GOm2Ih234QQAuw RT*DJGH?{f=Z|wTuzW~1}O7j2! literal 0 HcmV?d00001 diff --git a/tests/src/test_cases/widgets/test_scale.c b/tests/src/test_cases/widgets/test_scale.c new file mode 100644 index 000000000..41155e55a --- /dev/null +++ b/tests/src/test_cases/widgets/test_scale.c @@ -0,0 +1,367 @@ +#if LV_BUILD_TEST +#include "../lvgl.h" + +#include "unity/unity.h" + +/* Function run before every test */ +void setUp(void) +{ +} + +/* Function run after every test */ +void tearDown(void) +{ + lv_obj_clean(lv_screen_active()); +} + +/* A simple horizontal scale */ +void test_scale_render_example_1(void) +{ + lv_obj_t * scale = lv_scale_create(lv_screen_active()); + lv_obj_set_size(scale, lv_pct(80), 100); + lv_scale_set_mode(scale, LV_SCALE_MODE_HORIZONTAL_BOTTOM); + lv_obj_center(scale); + + lv_scale_set_label_show(scale, true); + + lv_scale_set_total_tick_count(scale, 31); + lv_scale_set_major_tick_every(scale, 5); + + lv_scale_set_major_tick_length(scale, 10); + lv_scale_set_minor_tick_length(scale, 5); + lv_scale_set_range(scale, 10, 40); + + TEST_ASSERT_EQUAL_SCREENSHOT("scale_1.png"); +} + +/* An vertical scale with section and custom styling */ +void test_scale_render_example_2(void) +{ + lv_obj_t * scale = lv_scale_create(lv_screen_active()); + lv_obj_set_size(scale, 60, 200); + lv_scale_set_label_show(scale, true); + lv_scale_set_mode(scale, LV_SCALE_MODE_VERTICAL_RIGHT); + lv_obj_center(scale); + + lv_scale_set_total_tick_count(scale, 21); + lv_scale_set_major_tick_every(scale, 5); + + lv_scale_set_major_tick_length(scale, 10); + lv_scale_set_minor_tick_length(scale, 5); + lv_scale_set_range(scale, 0, 100); + + static const char * custom_labels[] = {"0 °C", "25 °C", "50 °C", "75 °C", "100 °C", NULL}; + lv_scale_set_text_src(scale, custom_labels); + + static lv_style_t indicator_style; + lv_style_init(&indicator_style); + + /* Label style properties */ + lv_style_set_text_font(&indicator_style, &lv_font_montserrat_14); + lv_style_set_text_color(&indicator_style, lv_palette_darken(LV_PALETTE_BLUE, 3)); + + /* Major tick properties */ + lv_style_set_line_color(&indicator_style, lv_palette_darken(LV_PALETTE_BLUE, 3)); + lv_style_set_width(&indicator_style, 10U); /*Tick length*/ + lv_style_set_line_width(&indicator_style, 2U); /*Tick width*/ + lv_obj_add_style(scale, &indicator_style, LV_PART_INDICATOR); + + static lv_style_t minor_ticks_style; + lv_style_init(&minor_ticks_style); + lv_style_set_line_color(&minor_ticks_style, lv_palette_lighten(LV_PALETTE_BLUE, 2)); + lv_style_set_width(&minor_ticks_style, 5U); /*Tick length*/ + lv_style_set_line_width(&minor_ticks_style, 2U); /*Tick width*/ + lv_obj_add_style(scale, &minor_ticks_style, LV_PART_ITEMS); + + static lv_style_t main_line_style; + lv_style_init(&main_line_style); + /* Main line properties */ + lv_style_set_line_color(&main_line_style, lv_palette_darken(LV_PALETTE_BLUE, 3)); + lv_style_set_line_width(&main_line_style, 2U); // Tick width + lv_obj_add_style(scale, &main_line_style, LV_PART_MAIN); + + /* Add a section */ + static lv_style_t section_minor_tick_style; + static lv_style_t section_label_style; + static lv_style_t section_main_line_style; + + lv_style_init(§ion_label_style); + lv_style_init(§ion_minor_tick_style); + lv_style_init(§ion_main_line_style); + + /* Label style properties */ + lv_style_set_text_font(§ion_label_style, &lv_font_montserrat_14); + lv_style_set_text_color(§ion_label_style, lv_palette_darken(LV_PALETTE_RED, 3)); + + lv_style_set_line_color(§ion_label_style, lv_palette_darken(LV_PALETTE_RED, 3)); + lv_style_set_line_width(§ion_label_style, 5U); /*Tick width*/ + + lv_style_set_line_color(§ion_minor_tick_style, lv_palette_lighten(LV_PALETTE_RED, 2)); + lv_style_set_line_width(§ion_minor_tick_style, 4U); /*Tick width*/ + + /* Main line properties */ + lv_style_set_line_color(§ion_main_line_style, lv_palette_darken(LV_PALETTE_RED, 3)); + lv_style_set_line_width(§ion_main_line_style, 4U); /*Tick width*/ + + /* Configure section styles */ + lv_scale_section_t * section = lv_scale_add_section(scale); + lv_scale_section_set_range(section, 75, 100); + lv_scale_section_set_style(section, LV_PART_INDICATOR, §ion_label_style); + lv_scale_section_set_style(section, LV_PART_ITEMS, §ion_minor_tick_style); + lv_scale_section_set_style(section, LV_PART_MAIN, §ion_main_line_style); + + lv_obj_set_style_bg_color(scale, lv_palette_main(LV_PALETTE_BLUE_GREY), 0); + lv_obj_set_style_bg_opa(scale, LV_OPA_50, 0); + lv_obj_set_style_pad_left(scale, 8, 0); + lv_obj_set_style_radius(scale, 8, 0); + lv_obj_set_style_pad_ver(scale, 20, 0); + + TEST_ASSERT_EQUAL_SCREENSHOT("scale_2.png"); +} + +/* A simple round scale */ +void test_scale_render_example_3(void) +{ + lv_obj_t * scale = lv_scale_create(lv_screen_active()); + lv_obj_set_size(scale, 150, 150); + lv_scale_set_mode(scale, LV_SCALE_MODE_ROUND_INNER); + lv_obj_set_style_bg_opa(scale, LV_OPA_COVER, 0); + lv_obj_set_style_bg_color(scale, lv_palette_lighten(LV_PALETTE_RED, 5), 0); + lv_obj_set_style_radius(scale, LV_RADIUS_CIRCLE, 0); + lv_obj_center(scale); + + lv_scale_set_label_show(scale, true); + + lv_scale_set_total_tick_count(scale, 11); + lv_scale_set_major_tick_every(scale, 5); + + lv_scale_set_major_tick_length(scale, 10); + lv_scale_set_minor_tick_length(scale, 5); + lv_scale_set_range(scale, 10, 40); + + TEST_ASSERT_EQUAL_SCREENSHOT("scale_3.png"); +} + +/* A round scale with section and custom styling */ +void test_scale_render_example_4(void) +{ + lv_obj_t * scale = lv_scale_create(lv_screen_active()); + lv_obj_set_size(scale, 150, 150); + lv_scale_set_label_show(scale, true); + lv_scale_set_mode(scale, LV_SCALE_MODE_ROUND_OUTER); + lv_obj_center(scale); + + lv_scale_set_total_tick_count(scale, 21); + lv_scale_set_major_tick_every(scale, 5); + + lv_scale_set_major_tick_length(scale, 10); + lv_scale_set_minor_tick_length(scale, 5); + lv_scale_set_range(scale, 0, 100); + + static const char * custom_labels[] = {"0 °C", "25 °C", "50 °C", "75 °C", "100 °C", NULL}; + lv_scale_set_text_src(scale, custom_labels); + + static lv_style_t indicator_style; + lv_style_init(&indicator_style); + + /* Label style properties */ + lv_style_set_text_font(&indicator_style, &lv_font_montserrat_14); + lv_style_set_text_color(&indicator_style, lv_palette_darken(LV_PALETTE_BLUE, 3)); + + /* Major tick properties */ + lv_style_set_line_color(&indicator_style, lv_palette_darken(LV_PALETTE_BLUE, 3)); + lv_style_set_width(&indicator_style, 10U); /*Tick length*/ + lv_style_set_line_width(&indicator_style, 2U); /*Tick width*/ + lv_obj_add_style(scale, &indicator_style, LV_PART_INDICATOR); + + static lv_style_t minor_ticks_style; + lv_style_init(&minor_ticks_style); + lv_style_set_line_color(&minor_ticks_style, lv_palette_lighten(LV_PALETTE_BLUE, 2)); + lv_style_set_width(&minor_ticks_style, 5U); /*Tick length*/ + lv_style_set_line_width(&minor_ticks_style, 2U); /*Tick width*/ + lv_obj_add_style(scale, &minor_ticks_style, LV_PART_ITEMS); + + static lv_style_t main_line_style; + lv_style_init(&main_line_style); + /* Main line properties */ + lv_style_set_arc_color(&main_line_style, lv_palette_darken(LV_PALETTE_BLUE, 3)); + lv_style_set_arc_width(&main_line_style, 2U); /*Tick width*/ + lv_obj_add_style(scale, &main_line_style, LV_PART_MAIN); + + /* Add a section */ + static lv_style_t section_minor_tick_style; + static lv_style_t section_label_style; + static lv_style_t section_main_line_style; + + lv_style_init(§ion_label_style); + lv_style_init(§ion_minor_tick_style); + lv_style_init(§ion_main_line_style); + + /* Label style properties */ + lv_style_set_text_font(§ion_label_style, &lv_font_montserrat_14); + lv_style_set_text_color(§ion_label_style, lv_palette_darken(LV_PALETTE_RED, 3)); + + lv_style_set_line_color(§ion_label_style, lv_palette_darken(LV_PALETTE_RED, 3)); + lv_style_set_line_width(§ion_label_style, 5U); /*Tick width*/ + + lv_style_set_line_color(§ion_minor_tick_style, lv_palette_lighten(LV_PALETTE_RED, 2)); + lv_style_set_line_width(§ion_minor_tick_style, 4U); /*Tick width*/ + + /* Main line properties */ + lv_style_set_arc_color(§ion_main_line_style, lv_palette_darken(LV_PALETTE_RED, 3)); + lv_style_set_arc_width(§ion_main_line_style, 4U); /*Tick width*/ + + /* Configure section styles */ + lv_scale_section_t * section = lv_scale_add_section(scale); + lv_scale_section_set_range(section, 75, 100); + lv_scale_section_set_style(section, LV_PART_INDICATOR, §ion_label_style); + lv_scale_section_set_style(section, LV_PART_ITEMS, §ion_minor_tick_style); + lv_scale_section_set_style(section, LV_PART_MAIN, §ion_main_line_style); + + TEST_ASSERT_EQUAL_SCREENSHOT("scale_4.png"); +} + +void test_scale_set_style(void) +{ + lv_obj_t * scale = lv_scale_create(lv_screen_active()); + + static lv_style_t section_minor_tick_style; + static lv_style_t section_label_style; + static lv_style_t section_main_line_style; + + lv_style_init(§ion_label_style); + lv_style_init(§ion_minor_tick_style); + lv_style_init(§ion_main_line_style); + + /* Configure section styles */ + lv_scale_section_t * section = lv_scale_add_section(scale); + lv_scale_section_set_range(section, 75, 100); + + lv_scale_section_set_style(section, LV_PART_MAIN, §ion_main_line_style); + TEST_ASSERT_NOT_NULL(section->main_style); + TEST_ASSERT_NULL(section->indicator_style); + TEST_ASSERT_NULL(section->items_style); + + TEST_ASSERT_EQUAL(section->main_style, §ion_main_line_style); + + lv_scale_section_set_style(section, LV_PART_INDICATOR, §ion_label_style); + TEST_ASSERT_NOT_NULL(section->main_style); + TEST_ASSERT_NOT_NULL(section->indicator_style); + TEST_ASSERT_NULL(section->items_style); + + TEST_ASSERT_EQUAL(section->main_style, §ion_main_line_style); + TEST_ASSERT_EQUAL(section->indicator_style, §ion_label_style); + + lv_scale_section_set_style(section, LV_PART_ITEMS, §ion_minor_tick_style); + TEST_ASSERT_NOT_NULL(section->main_style); + TEST_ASSERT_NOT_NULL(section->indicator_style); + TEST_ASSERT_NOT_NULL(section->items_style); + + TEST_ASSERT_EQUAL(section->main_style, §ion_main_line_style); + TEST_ASSERT_EQUAL(section->indicator_style, §ion_label_style); + TEST_ASSERT_EQUAL(section->items_style, §ion_minor_tick_style); + + /* Invalid part */ + lv_scale_section_set_style(section, LV_PART_CURSOR, §ion_minor_tick_style); + TEST_ASSERT_NOT_NULL(section->main_style); + TEST_ASSERT_NOT_NULL(section->indicator_style); + TEST_ASSERT_NOT_NULL(section->items_style); + + TEST_ASSERT_EQUAL(section->main_style, §ion_main_line_style); + TEST_ASSERT_EQUAL(section->indicator_style, §ion_label_style); + TEST_ASSERT_EQUAL(section->items_style, §ion_minor_tick_style); + + /* NULL section */ + lv_scale_section_t * null_section = NULL; + + lv_scale_section_set_range(null_section, 75, 100); + lv_scale_section_set_style(null_section, LV_PART_MAIN, §ion_main_line_style); +} + +/* The scale internally counts the number of custom labels until it finds the NULL sentinel */ +void test_scale_custom_labels_count(void) +{ + lv_obj_t * scale = lv_scale_create(lv_screen_active()); + lv_scale_set_label_show(scale, true); + + static const char * custom_labels[] = {"0 °C", "25 °C", "50 °C", "75 °C", "100 °C", NULL}; + lv_scale_set_text_src(scale, custom_labels); + + lv_scale_t * scale_widget = (lv_scale_t *)scale; + + TEST_ASSERT_EQUAL(5U, scale_widget->custom_label_cnt); + + static const char * animal_labels[] = {"cat", "dog", NULL}; + lv_scale_set_text_src(scale, animal_labels); + + TEST_ASSERT_EQUAL(2U, scale_widget->custom_label_cnt); +} + +void test_scale_mode(void) +{ + lv_obj_t * scale = lv_scale_create(lv_screen_active()); + + lv_scale_mode_t mode = LV_SCALE_MODE_ROUND_INNER; + lv_scale_set_mode(scale, mode); + + TEST_ASSERT_EQUAL(mode, lv_scale_get_mode(scale)); +} + +void test_scale_total_tick_count(void) +{ + lv_obj_t * scale = lv_scale_create(lv_screen_active()); + + uint32_t total_tick_count = 42; + lv_scale_set_total_tick_count(scale, total_tick_count); + + TEST_ASSERT_EQUAL(total_tick_count, lv_scale_get_total_tick_count(scale)); +} + +void test_scale_major_tick_every(void) +{ + lv_obj_t * scale = lv_scale_create(lv_screen_active()); + + uint32_t major_tick_every = 6; + lv_scale_set_major_tick_every(scale, major_tick_every); + + TEST_ASSERT_EQUAL(major_tick_every, lv_scale_get_major_tick_every(scale)); +} + +void test_scale_label_show(void) +{ + lv_obj_t * scale = lv_scale_create(lv_screen_active()); + + bool label_show = true; + lv_scale_set_label_show(scale, label_show); + + TEST_ASSERT_EQUAL(label_show, lv_scale_get_label_show(scale)); + + label_show = false; + lv_scale_set_label_show(scale, label_show); + + TEST_ASSERT_EQUAL(label_show, lv_scale_get_label_show(scale)); +} + +void test_scale_angle_range(void) +{ + lv_obj_t * scale = lv_scale_create(lv_screen_active()); + + uint32_t angle_range = 42; + lv_scale_set_angle_range(scale, angle_range); + + TEST_ASSERT_EQUAL(angle_range, lv_scale_get_angle_range(scale)); +} + +void test_scale_range(void) +{ + lv_obj_t * scale = lv_scale_create(lv_screen_active()); + + int32_t min_range = 24; + int32_t max_range = 42; + lv_scale_set_range(scale, min_range, max_range); + + TEST_ASSERT_EQUAL(min_range, lv_scale_get_range_min_value(scale)); + TEST_ASSERT_EQUAL(max_range, lv_scale_get_range_max_value(scale)); +} + +#endif