From 0558954eb67d2a72842cbb5b86c3c12be7a4a74e Mon Sep 17 00:00:00 2001 From: andreamah Date: Sat, 4 Apr 2020 13:58:17 -0700 Subject: [PATCH 01/11] made non-library functions private --- src/base_circuitpython/board.py | 6 ++-- src/base_circuitpython/displayio/group.py | 12 ++++---- .../displayio/test/test_group.py | 3 +- .../displayio/test/test_tile_grid.py | 4 +-- src/base_circuitpython/displayio/tile_grid.py | 10 +++---- src/base_circuitpython/terminal_handler.py | 6 ++-- src/clue/adafruit_slideshow.py | 30 +++++++++---------- src/clue/test/test_adafruit_clue.py | 4 +-- src/clue/test/test_adafruit_display_shapes.py | 4 +-- src/clue/test/test_adafruit_display_text.py | 2 +- src/clue/test/test_adafruit_slideshow.py | 6 ++-- 11 files changed, 43 insertions(+), 44 deletions(-) diff --git a/src/base_circuitpython/board.py b/src/base_circuitpython/board.py index 62de38b1e..5ba1bd8d4 100644 --- a/src/base_circuitpython/board.py +++ b/src/base_circuitpython/board.py @@ -15,14 +15,14 @@ def show(self, group=None): self.active_group = group if group == None: - self.terminal.draw() + self.terminal._Terminal__draw() return # if the group has no attribute called # "draw", then it is liable for updating itself # when it calls show - if hasattr(group, "draw"): - group.draw() + if hasattr(group, "_Group__draw"): + group._Group__draw() DISPLAY = Display() diff --git a/src/base_circuitpython/displayio/group.py b/src/base_circuitpython/displayio/group.py index 17109b03a..988c638e7 100644 --- a/src/base_circuitpython/displayio/group.py +++ b/src/base_circuitpython/displayio/group.py @@ -50,7 +50,7 @@ def elem_changed(self): def trigger_draw(self): # select the correct parent to draw from if necessary if self.__check_active_group_ref and board.DISPLAY.active_group == self: - self.draw() + self.__draw() elif self.in_group: # If a sub-group is modified, propagate to top level to @@ -67,7 +67,7 @@ def __setitem__(self, index, val): if old_val != val: self.elem_changed() - def draw(self, img=None, x=0, y=0, scale=None, show=True): + def __draw(self, img=None, x=0, y=0, scale=None, show=True): # this function is not a part of the orignal implementation # it is what draws itself and its children and potentially shows it to the # frontend @@ -104,18 +104,18 @@ def draw(self, img=None, x=0, y=0, scale=None, show=True): for elem in self.__contents: if isinstance(elem, Group): - img = elem.draw(img=img, x=x, y=y, scale=scale, show=False,) + img = elem._Group__draw(img=img, x=x, y=y, scale=scale, show=False,) else: - img = elem.draw(img=img, x=x, y=y, scale=scale) + img = elem._TileGrid__draw(img=img, x=x, y=y, scale=scale) # show should only be true to the highest parent group if show: - self.show(img) + self.__show(img) # return value only used if this is within another group return img - def show(self, img): + def __show(self, img): # sends current img to the frontend buffered = BytesIO() img.save(buffered, format="BMP") diff --git a/src/base_circuitpython/displayio/test/test_group.py b/src/base_circuitpython/displayio/test/test_group.py index 48325663d..7eee71159 100644 --- a/src/base_circuitpython/displayio/test/test_group.py +++ b/src/base_circuitpython/displayio/test/test_group.py @@ -155,8 +155,7 @@ def test_draw_group( group_sub.append(tg) group_main.append(group_sub) group_main.append(tg2) - # img = Image.new("RGBA", (240, 240)) - img = group_main.draw() + img = group_main._Group__draw() img.putalpha(255) expected = Image.open( diff --git a/src/base_circuitpython/displayio/test/test_tile_grid.py b/src/base_circuitpython/displayio/test/test_tile_grid.py index 8c4a591b2..b37a8048a 100644 --- a/src/base_circuitpython/displayio/test/test_tile_grid.py +++ b/src/base_circuitpython/displayio/test/test_tile_grid.py @@ -140,7 +140,7 @@ def test_draw( "RGBA", (CONSTANTS.SCREEN_HEIGHT_WIDTH, CONSTANTS.SCREEN_HEIGHT_WIDTH) ) # without scaling, test output - img = tg.draw(img, x_offset, y_offset, 1) + img = tg._TileGrid__draw(img, x_offset, y_offset, 1) bmp_img = img.load() for i in range(CONSTANTS.SCREEN_HEIGHT_WIDTH): for j in range(CONSTANTS.SCREEN_HEIGHT_WIDTH): @@ -157,7 +157,7 @@ def test_draw( "RGBA", (CONSTANTS.SCREEN_HEIGHT_WIDTH, CONSTANTS.SCREEN_HEIGHT_WIDTH) ) # with scaling, test output - img = tg.draw(img, x_offset, y_offset, scale) + img = tg._TileGrid__draw(img, x_offset, y_offset, scale) bmp_img = img.load() for i in range(CONSTANTS.SCREEN_HEIGHT_WIDTH): for j in range(CONSTANTS.SCREEN_HEIGHT_WIDTH): diff --git a/src/base_circuitpython/displayio/tile_grid.py b/src/base_circuitpython/displayio/tile_grid.py index 08610085b..22558fecc 100644 --- a/src/base_circuitpython/displayio/tile_grid.py +++ b/src/base_circuitpython/displayio/tile_grid.py @@ -71,20 +71,20 @@ def __getitem__(self, index): # methods that are not in the origin class: - def draw(self, img, x, y, scale): + def __draw(self, img, x, y, scale): # draw the current bitmap with # appropriate scale on the global bmp_img x = self.x * scale + x y = self.y * scale + y - new_shape = self.draw_group( + new_shape = self.__draw_group( x, y, 0, self.tile_height, 0, self.tile_width, scale ) img.paste(new_shape, (x, y), new_shape) return img - def draw_group(self, x, y, y_start, y_end, x_start, x_end, scale): + def __draw_group(self, x, y, y_start, y_end, x_start, x_end, scale): height = y_end - y_start width = x_end - x_start @@ -106,7 +106,7 @@ def draw_group(self, x, y, y_start, y_end, x_start, x_end, scale): if not transparent and x_offset >= 0 and y_offset >= 0: curr_colour = self.pixel_shader[curr_val] - self.fill_pixel( + self.__fill_pixel( curr_val, curr_colour, x_offset, @@ -121,7 +121,7 @@ def draw_group(self, x, y, y_start, y_end, x_start, x_end, scale): # helper method for drawing pixels on bmp_img # given the src, offset, and scale - def fill_pixel( + def __fill_pixel( self, curr_val, curr_colour, diff --git a/src/base_circuitpython/terminal_handler.py b/src/base_circuitpython/terminal_handler.py index 2fff004e5..83130f0b0 100644 --- a/src/base_circuitpython/terminal_handler.py +++ b/src/base_circuitpython/terminal_handler.py @@ -37,7 +37,7 @@ def __create_newline(self, str_list): self.__lock.release() - def draw(self, no_verif=False): + def __draw(self, no_verif=False): import adafruit_display_text.label @@ -74,7 +74,7 @@ def draw(self, no_verif=False): self.__lock.release() - splash.draw(img=self.__base_img.copy()) + splash._Group__draw(img=self.__base_img.copy()) def add_str_to_terminal(self, curr_display_string=""): @@ -105,4 +105,4 @@ def add_str_to_terminal(self, curr_display_string=""): # only go ahead to draw the screen # if the terminal is actively on the screen if board.DISPLAY.active_group == None: - self.draw() + self.__draw() diff --git a/src/clue/adafruit_slideshow.py b/src/clue/adafruit_slideshow.py index cef98428b..225cc384c 100644 --- a/src/clue/adafruit_slideshow.py +++ b/src/clue/adafruit_slideshow.py @@ -76,7 +76,7 @@ def __init__( """Specify the playback direction. Default is ``PlayBackDirection.FORWARD``. Can also be ``PlayBackDirection.BACKWARD``.""" - self.advance = self._advance_with_fade + self.advance = self.__advance_with_fade """Displays the next image. Returns True when a new image was displayed, False otherwise. """ @@ -84,7 +84,7 @@ def __init__( # assign new advance method if fade is disabled if not fade_effect: - self.advance = self._advance_no_fade + self.advance = self.__advance_no_fade self._img_start = None @@ -111,7 +111,7 @@ def __init__( self._curr_img = "" # load images into main queue - self._load_images() + self.__load_images() display.show(self) # show the first working image @@ -134,7 +134,7 @@ def order(self, order): raise ValueError("Order must be either 'RANDOM' or 'ALPHABETICAL'") self._order = order - self._load_images() + self.__load_images() @property def brightness(self): @@ -157,12 +157,12 @@ def update(self): return self.advance() - def _get_next_img(self): + def __get_next_img(self): # handle empty queue if not len(self.pic_queue): if self.loop: - self._load_images() + self.__load_images() else: return "" @@ -171,7 +171,7 @@ def _get_next_img(self): else: return self.pic_queue.pop() - def _load_images(self): + def __load_images(self): dir_imgs = [] for d in self.dirs: try: @@ -195,7 +195,7 @@ def _load_images(self): # (must be list beforehand for potential randomization) self.pic_queue = collections.deque(dir_imgs) - def _advance_with_fade(self): + def __advance_with_fade(self): if board.DISPLAY.active_group != self: return @@ -203,7 +203,7 @@ def _advance_with_fade(self): advance_sucessful = False while not advance_sucessful: - new_path = self._get_next_img() + new_path = self.__get_next_img() if new_path == "": return False @@ -236,7 +236,7 @@ def _advance_with_fade(self): sendable_img = Image.blend( black_overlay, old_img, i * self.brightness / self.fade_frames ) - self._send(sendable_img) + self.__send(sendable_img) time.sleep(self._BASE_DWELL_DARK) @@ -245,14 +245,14 @@ def _advance_with_fade(self): sendable_img = Image.blend( black_overlay, new_img, i * self.brightness / self.fade_frames ) - self._send(sendable_img) + self.__send(sendable_img) self._curr_img_handle = new_img self._curr_img = new_path self._img_start = time.monotonic() return True - def _advance_no_fade(self): + def __advance_no_fade(self): if board.DISPLAY.active_group != self: return @@ -261,7 +261,7 @@ def _advance_no_fade(self): advance_sucessful = False while not advance_sucessful: - new_path = self._get_next_img() + new_path = self.__get_next_img() if new_path == "": return False @@ -303,14 +303,14 @@ def _advance_no_fade(self): ) img_piece = new_img.crop((0, 0, CONSTANTS.SCREEN_HEIGHT_WIDTH, curr_y)) old_img.paste(img_piece) - self._send(old_img) + self.__send(old_img) self._curr_img_handle = new_img self._curr_img = new_path self._img_start = time.monotonic() return True - def _send(self, img): + def __send(self, img): # sends current bmp_img to the frontend buffered = BytesIO() img.save(buffered, format=CONSTANTS.BMP_IMG) diff --git a/src/clue/test/test_adafruit_clue.py b/src/clue/test/test_adafruit_clue.py index 4b6e60e50..dadde4998 100644 --- a/src/clue/test/test_adafruit_clue.py +++ b/src/clue/test/test_adafruit_clue.py @@ -38,7 +38,7 @@ def test_clue_display_text(self): expected = img.load() clue_data = clue.simple_text_display(title="LET'S TEST!", title_scale=2) - clue_data.text_group.show = self._send_helper + clue_data.text_group._Group__show = self.__send_helper clue_data.text_group._Group__check_active_group_ref = False clue_data[0].text = "Lorem ipsum" @@ -58,7 +58,7 @@ def test_clue_display_text(self): clue_data.show() helper._Helper__test_image_equality(self.main_img.load(), expected) - def _send_helper(self, image): + def __send_helper(self, image): self.main_img = image def test_buttons(self): diff --git a/src/clue/test/test_adafruit_display_shapes.py b/src/clue/test/test_adafruit_display_shapes.py index 0036b2e73..8c1c3c303 100644 --- a/src/clue/test/test_adafruit_display_shapes.py +++ b/src/clue/test/test_adafruit_display_shapes.py @@ -48,7 +48,7 @@ def test_shapes(self): # TAKEN FROM ADAFRUIT'S DISPLAY SHAPES LIBRARY # https://github.com/ladyada/Adafruit_CircuitPython_Display_Shapes/blob/master/examples/display_shapes_simpletest.py splash = displayio.Group(max_size=10) - splash.show = self._send_helper + splash._Group__show = self.__send_helper board.DISPLAY.show(splash) color_bitmap = displayio.Bitmap(320, 240, 1) color_palette = displayio.Palette(1) @@ -77,5 +77,5 @@ def test_shapes(self): helper._Helper__test_image_equality(self.main_img.load(), expected_images[4]) - def _send_helper(self, image): + def __send_helper(self, image): self.main_img = image diff --git a/src/clue/test/test_adafruit_display_text.py b/src/clue/test/test_adafruit_display_text.py index 5d6078704..922230275 100644 --- a/src/clue/test/test_adafruit_display_text.py +++ b/src/clue/test/test_adafruit_display_text.py @@ -59,7 +59,7 @@ def test_display_text(self, text, x, y, scale, color): text_area.x = x text_area.y = y - main_img = text_area.draw() + main_img = text_area._Group__draw() helper._Helper__test_image_equality(main_img.load(), loaded_img) test_count += 1 diff --git a/src/clue/test/test_adafruit_slideshow.py b/src/clue/test/test_adafruit_slideshow.py index 649e3ccb4..1f966b15a 100644 --- a/src/clue/test/test_adafruit_slideshow.py +++ b/src/clue/test/test_adafruit_slideshow.py @@ -61,7 +61,7 @@ def test_slideshow(self): direction=PlayBackDirection.FORWARD, ) - slideshow._send = self._send_helper + slideshow._SlideShow__send = self.__send_helper # first image's appear time is unstable,since it fades/scrolls in # can only predict following ones... @@ -84,7 +84,7 @@ def test_slideshow(self): direction=PlayBackDirection.BACKWARD, ) - slideshow2._send = self._send_helper + slideshow2._SlideShow__send = self.__send_helper helper._Helper__test_image_equality( self.main_img.load(), slideshow_images[7].load() @@ -96,5 +96,5 @@ def test_slideshow(self): self.main_img.load(), slideshow_images[i].load() ) - def _send_helper(self, image): + def __send_helper(self, image): self.main_img = image From a96afb856e398bf63724192f401df271423ba104 Mon Sep 17 00:00:00 2001 From: andreamah Date: Sat, 4 Apr 2020 17:46:39 -0700 Subject: [PATCH 02/11] implemented unimplemented methods --- ...n-display-text-DSX_CUSTOM_MAR172020.tar.gz | Bin 126478 -> 126485 bytes src/base_circuitpython/displayio/__init__.py | 1 - .../displayio/color_type.py | 4 +- src/base_circuitpython/displayio/group.py | 79 +++++++++++++++--- src/base_circuitpython/displayio/palette.py | 4 +- src/base_circuitpython/displayio/tile_grid.py | 48 ++++++++++- src/base_circuitpython/pulseio.py | 1 + src/clue/adafruit_clue.py | 4 - src/clue/adafruit_slideshow.py | 56 +++++++++++++ 9 files changed, 175 insertions(+), 22 deletions(-) create mode 100644 src/base_circuitpython/pulseio.py diff --git a/adafruit-circuitpython-display-text-DSX_CUSTOM_MAR172020.tar.gz b/adafruit-circuitpython-display-text-DSX_CUSTOM_MAR172020.tar.gz index 3fc33f13aabba65c283c5347ede379ecd2ca58c4..85b10a468cf9ee20f2f94ad5348619c699710970 100644 GIT binary patch delta 16868 zcmV(=K-s^J+y|B12L~UE2nZl1iGc^T2LTMMe@QL@g8;{o6M-AH!{ZTxa*|H=5lJU> zQf$$C@4ffl+oShK@4a{U=)Hb){+Zpq+dH3I5<<&Co^^Y>Gqba^v$M0ivp$IYy0irA z*|12}xMDo!F786fS9|DwHqV@dj<#JnV2Z^(8bho$IZp`|tV0YGNzUmyvY#~jQOCBu ze*}z1A)C!)NjjU-Qb4~^HVK%}g?A2cH1mX&4d-ojxQ{JaO^e$)B?*p{dyvVpDD!y8 zKrX&v@*su|ZPiB31kpZ#7RY0}G!_cCzFmWg5z7aL)vQX#b&RvejCjR#E$R~F2q!}( z;jgoB7Dcn~_UDnjRyUWpP1}xN2gzVXe{X z^KM=A*799Z0aQg=cvh8jiPj?}&x=UG#>A*Ab89O#PKV4FlLkZ=i7YO4e~_c@FWXb% zWX~zfh6A3JVfFLsR0AetM`}=28uoycJ_X$70fq4Sq`_}X+DUXqVt5iwBH@+Ue`vyh zEfk*(-ETGQFvqx)DQ49iVx3%P6Fs4$I01Q(wa^yKFpvnQraY3UIfI>vBu9t|W02KS zG^qKN8$FU3aZw2Xq{7M<&9bICoe<#UO;5ArXF}9y>eE~^TnF=d|WDq)!dC#*X2_0j% z#X7-Xe5BR3F3B)?@M$^*cyc<>b)iZ?BR)nD0|A$WRLOh7YK?7jyCNnESfqork@R{@ z)!3+48Y&NDLIu&^qfOgeuJu-OHGV5gsO-Q)Bkdkq4Kod7?1*M=IZ$9_e+6D@OjQR; zgFvbve@QvRP(}wjq;klx z9S30KJN9Q6Wp6g%eFQWigpDphLOghuMuRhVBr>9fP6wA2I?$<#!#paNlBG&nTItFP z(LE&kW(>x~W?{F{=xMx(+i5FJrPxrY_oVsB({C~ZIKR(|RC{SPUmCdaJ>#WgX6o+a zsXJv-H!mIE@4p-$f4KFH6Zhm|_q>Z#9i@BTMewvF$(2(wUD%mgHZKES9*l(nGv|<1 zGrM)NKDjWU34YZrv8kZjMDO50t2+E=aYDMV?Im4_uo{$O09|?S^ zM4LoPC7vEp8_}zDEMl$@5S(Z=tjiH8BQmclK;;$ylJyinv_h>Y(Z zQ(Jms1|FE_!;&a=>c;NeK~k2DKeL?hF$yJ-@25ijf9ttp;7Mqk6_AD+(#T8$Lfuld zV@MFqQn#8*on^QQGwT6jwvzapQj$e!@mn$-#WO+icUnS^6Wc&-)-cA5Z1T>{TrM|e z9{^AHc?y0)z=5y{zC+SFD;9<%{>mm=rS1^tXkGwIurr2Uku(wl77IoJOp|oo{ zKJvB|r;AfCO%{uuWm4oWPEA5IOPc|2@R9;jV84y85L9j&&~-56Ko;F+i-7$6VNhaV z*eXFXv&Y$KX|A%AXuZw!%lFulYo`1J+1rLCvks0g6elE?X3PeVYgVB?@Ph8Z$SBO` ze|Vh6^5gU$9@_7bOAcl3m5>9z80`}YsW#^~usSdX)5g&Cal(|?9=2d@v^kLiO$qzZx+yL%Ed3n$sZ zpr1sSHlta`y?SkZL}&MsbzUBWd55Z$e~#0SU^P)o*i@y`ws^Tk3c%d$;f}1Sg1%3@ zYV{(BfUB9WfU`*^ARpLvO+yr*UK`=Y=V+|ab)?bM^t%|n2m7>y<=N|r(`&OxJfL+; z7yAmk2m-PMGE?XG}sPNIYGsu;Tpn$n?St|S;fji2CNoAv1f2NSC zs;KvWrTSP8=k_(oVaM633Q<1jXdObpmK;Z^5j%O;B4EP=k083k%| zi7{_oP+mCYW|0S9EIC;P)Kd_dI%O9A0p<+{ILohB=U`{1iG$#RuR_$we-iu8Y#n!{Q-EIauzBsLKfZ{n9xx=azv#s1u@;n;%04W5@p;{_I z`bs6wwW&(-KD!js{Mj5YN*P$nqg9h0M!97}dL_sSRra>_sAUDCjP_LPW;vP}6Tl&H zY^X!tTRwG@^ww3(rt5FhU<7gn3~n!xmp_Zu53-ho?AuM@4{ZaN&9wm{f8&t1+fQ*- z&>mU=+Q=x^HViTdZG|e?Imv{lJNBwnLF|x5;ib0OTBbf|ts(=tNC3wo&rZc=M~k*c zkT){K<<_40IXY6xZe##d1S+3Nf*fwj8<}Dx`E@bV85rRZ20lpz+SDT{5k(VVZ9-NA zOuw#TWeLDl>x((csOx74 zs4Wzj*g2x|ro^Y-8K5>vwYIDD?xE^=i1=HIzvbn|A*oL$rp6Z*!Yexgn?QAM=`ymU zEQl@432So1CmRzUXroXiJDo7KL zK-z*Z@bZB$&5dChYAbD z!!zq{(NkF0*`CLae_ev;=10~Ap(KJ~c>IaGmSxOmZl1{r-zWj{v*r%~bTB2GT@ORq zaiHe)9l+B2Y-<~31w6`{qI~PpEBAA3x*btuBf4_yLE>Enn{RC7Y=E;aMZ8kbS%-bu zgmli!T=@g*g6Wp)c17Y}2imZLCNg_{Hn*O$Uu~xuGSbG$nNGxqv2@{Ms2tu zC_iU@B;&aePNUL83E?*lS6Z!n7w$Mwm|HB)&L{<0kmn1O#j@h<$}yGJ5!OZ7qMsGB z3p>VJpLFH<(wGM(hE2Q}V8uQ#$fpNB?`7F@KK#tT`q@(a585vTrA%|BbgxnG+vc-r z{HFt>;rMU4f86=_@8{$ZTdK$Q>^AVAJGwUBjG;Zd<&2qCO^M9PF+)#EcUC*+yXSxZ zv!(uj%B7j;_5(kiM*OD(nH03>qzc={Qq3QfM_k zd(<)6n@SE05F^h-@LHM+^l&^_+qn&`%J5(5(yP&e?GZ#v9F7@4JJ5A4r{wce=Vw?@Xa8i z1Nq@qcm~5oqF{fLYBLC)x>lWtBOt_1r>4^=vH=nC2~@>Y}S3Wto? z_yXQzN#nxe;*r^-iw1JckIyV#WXw()<1-f-w=2#ZN*aYb&dnp7oiRI4(6FXBRV*O6 zI5RPI^big(8wXKzW)}USh>ifbi?f7Ne?h~BOM*9Dn4dU;*YShJsp8^ANnX1sF3u2S zY$P|vjk)pp#p1+K?9w#mj?T}`E)+n-AwZr%6uWsKDoht<7SlkCG^20_yci2d#;2wT z8!tm1MK$J$P=wu^yJ#N4@D`0Dvr~r(NIX~oeRME|uyO6Mno*oi8i&TG5vh+$f6W3A zRT4QRnc&Tl0;Lkxar}RR9DJyc2<(T~B&s{V=#{>(xPTaY*l4;yoSB@To#vW?yHseF z;84+-0s~^s2O9`k==~^auV5O73gc4%w}8Uo21O&sVsH$eh#Yd#Jmj3%vpatp4>>XA zsOB03&0uJ>#zO)h82CjqtD&k>i z`;VCIDhc)2{{13hLDyx6wBK?T*qiCT%a$Y~5LF1vPki$8YwFF|#6 z>G*KN8`K9Y0-l%v47RPhWx7PYYznnD>2rkAN+szlDwT}95zD0Uzm(bSe>mw(jK0ze zr1-QE>Ma-_X_R9s$Ov6Y}BLbffYuR-6Z(42wGutsf6o#S`a)wTeWKO~f1>4Fr(J>)(#V8Suj0dM zD6NMy^o&qy%dEpiXyKc$pXWqcJQw6`SXRXOmktvtIUNQ1SmrpOhYPQtKTSItO5&V< zT-7YZ9SUG3){#p-U|`_}@k_u~*qpM=CZy=twoA_<``{vRX9>4th1MUbe;~xu7m##V zQ^&)Ny=<{%pO=LJIV>`IFGfE7HJnvp0wPcT0#ZPdxmnAXx~;0&u~>@Z(D|aHC3M9Y z%t9)W&QJ3z>@{Pb48bwxMdj@{PO5=vrrd_0X~|$4_JXf=@YSh9{z3&xPOauz#6Td7 z2dfi$#S>~7?$~jsU5&@4e`AS+Y>c&~QeO37Pj!JOcIRdcvB&f#RBzGkLr+Nh28|pN z!rz#CZJTiP3|TkYfW#pP2zD&#Raz?q1A}he?sF5=N#KAHpNG1 z|2Y3ng4zKAL<|jUu|RfF9}<7LnTq2W;KA? zQLT>0Z)BoXyd*(ON)lV`tzVRPsnVPRq;nA+V>EIT= z%MO1lG2Z)mBV;Kcf7Lh2Yk-e2kmI4Q7$FHT-ovS?&zP0q7`q4`*WtEZ4c96xuj#Vg zX_&3#`q^6cs#Pz+YXiZ^>qE>2WvXI_PpJbc@&XV2EOB_(4a#vQ7T&xj$eR@q(va6h zBUB_xBJy!xHjL^gPj4UwG^`TjZ;WQ{fO17#(X`zo%J?K7>W+gl#DSNqAcE1~phm z#@CnSakbHu1$q-iq*Hwa8|Urmd3l}?Xmy{DMffw^lXi7tf3D-E(@e2_!qHgLAw^G!vcWWFbwv6;#zr%F4<>_D9vqaDZlpjt88Uzq0G_BW331&(Vq9$EV?2z;TNpr2U;>az%e5_YD zzL|-HM>#Rok{BPN105bLwNsf1iLYVGMs?M#<*2&yf>48|A3z@p48mSY<

~nHPD` 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 j7v}leSXo!y=FLFCq@ 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 self.color_count: diff --git a/src/base_circuitpython/displayio/tile_grid.py b/src/base_circuitpython/displayio/tile_grid.py index 22558fecc..5dfd747e9 100644 --- a/src/base_circuitpython/displayio/tile_grid.py +++ b/src/base_circuitpython/displayio/tile_grid.py @@ -45,11 +45,55 @@ def __init__( self.bitmap = bitmap self.pixel_shader = pixel_shader self.default_tile = default_tile - self.parent = None + self.__parent = None + + # unimplemented features + self.__flip_x = False + self.__flip_y = False + self.__transpose_xy = False @property def in_group(self): - return self.parent != None + return self.__parent != None + + # unimplemented features + self.__flip_x = False + self.__flip_y = False + self.__transpose_xy = False + + @property + def hidden(self): + if self.__parent == None: + return True + else: + return self.__parent.hidden + + @property + def flip_x(self): + return self.__flip_x + + @flip_x.setter + def flip_x(self, val): + print("feature not implemented") + self.__flip_x = val + + @property + def flip_y(self): + return self.__flip_y + + @flip_y.setter + def flip_y(self, val): + print("feature not implemented") + self.__flip_y = val + + @property + def transpose_xy(self): + return self.__transpose_xy + + @transpose_xy.setter + def transpose_xy(self, val): + print("feature not implemented") + self.__transpose_xy = val # setitem for an index simply gets the index of the bitmap # rather than the tile index diff --git a/src/base_circuitpython/pulseio.py b/src/base_circuitpython/pulseio.py new file mode 100644 index 000000000..f6632fb9e --- /dev/null +++ b/src/base_circuitpython/pulseio.py @@ -0,0 +1 @@ +print("pulseio is implemented on the device but not on the simulator") diff --git a/src/clue/adafruit_clue.py b/src/clue/adafruit_clue.py index 9225936f4..2b043df0a 100644 --- a/src/clue/adafruit_clue.py +++ b/src/clue/adafruit_clue.py @@ -161,14 +161,10 @@ def add_text_line(self, color=0xFFFFFF): def show(self): """Call show() to display the data list.""" self._display.show(self.text_group) - # https://stackoverflow.com/questions/31826335/how-to-convert-pil-image-image-object-to-base64-string def show_terminal(self): """Revert to terminalio screen.""" - self._display.show(None) - # TODO: implement terminal for clue screen - return class Clue: # pylint: disable=too-many-instance-attributes, too-many-public-methods diff --git a/src/clue/adafruit_slideshow.py b/src/clue/adafruit_slideshow.py index 225cc384c..f38e7b0cf 100644 --- a/src/clue/adafruit_slideshow.py +++ b/src/clue/adafruit_slideshow.py @@ -40,6 +40,62 @@ class PlayBackDirection: # custom class SlideShow: + """ + Class for displaying a slideshow of .bmp images on displays. + :param str folder: Specify the folder containing the image files, in quotes. Default is + the root directory, ``"/"``. + :param PlayBackOrder order: The order in which the images display. You can choose random + (``RANDOM``) or alphabetical (``ALPHABETICAL``). Default is + ``ALPHABETICAL``. + :param bool loop: Specify whether to loop the images or play through the list once. `True` + if slideshow will continue to loop, ``False`` if it will play only once. + Default is ``True``. + :param int dwell: The number of seconds each image displays, in seconds. Default is 3. + :param bool fade_effect: Specify whether to include the fade effect between images. ``True`` + tells the code to fade the backlight up and down between image display + transitions. ``False`` maintains max brightness on the backlight between + image transitions. Default is ``True``. + :param bool auto_advance: Specify whether to automatically advance after dwell seconds. ``True`` + if slideshow should auto play, ``False`` if you want to control advancement + manually. Default is ``True``. + :param PlayBackDirection direction: The playback direction. + Example code for Hallowing Express. With this example, the slideshow will play through once + in alphabetical order: + .. code-block:: python + from adafruit_slideshow import PlayBackOrder, SlideShow + import board + import pulseio + slideshow = SlideShow(board.DISPLAY, pulseio.PWMOut(board.TFT_BACKLIGHT), folder="/", + loop=False, order=PlayBackOrder.ALPHABETICAL) + while slideshow.update(): + pass + Example code for Hallowing Express. Sets ``dwell`` to 0 seconds, turns ``auto_advance`` off, + and uses capacitive touch to advance backwards and forwards through the images and to control + the brightness level of the backlight: + .. code-block:: python + from adafruit_slideshow import PlayBackOrder, SlideShow, PlayBackDirection + import touchio + import board + import pulseio + forward_button = touchio.TouchIn(board.TOUCH4) + back_button = touchio.TouchIn(board.TOUCH1) + brightness_up = touchio.TouchIn(board.TOUCH3) + brightness_down = touchio.TouchIn(board.TOUCH2) + slideshow = SlideShow(board.DISPLAY, pulseio.PWMOut(board.TFT_BACKLIGHT), folder="/", + auto_advance=False, dwell=0) + while True: + if forward_button.value: + slideshow.direction = PlayBackDirection.FORWARD + slideshow.advance() + if back_button.value: + slideshow.direction = PlayBackDirection.BACKWARD + slideshow.advance() + if brightness_up.value: + slideshow.brightness += 0.001 + elif brightness_down.value: + slideshow.brightness -= 0.001 + """ + def __init__( self, display, From 450cfe54f1b9096f3fae3faad945c02b465c2cde Mon Sep 17 00:00:00 2001 From: andreamah Date: Sun, 5 Apr 2020 22:59:55 -0700 Subject: [PATCH 03/11] properly printed unimplemented methods, added board consts, changed hidden for group and tilegrid --- src/base_circuitpython/board.py | 96 ++++++++++++++++++- src/base_circuitpython/displayio/group.py | 28 ++++-- src/base_circuitpython/displayio/tile_grid.py | 17 ++-- src/base_circuitpython/pulseio.py | 17 +++- 4 files changed, 135 insertions(+), 23 deletions(-) diff --git a/src/base_circuitpython/board.py b/src/base_circuitpython/board.py index 5ba1bd8d4..92f501408 100644 --- a/src/base_circuitpython/board.py +++ b/src/base_circuitpython/board.py @@ -27,8 +27,102 @@ def show(self, group=None): DISPLAY = Display() -# deafult pin, +# default pin for neopixel, # shows that this could # refer to the CPX # or CLUE neopixel pin NEOPIXEL = "D00" + +# ETC PINS WITHIN THE CLUE THAT MAY BE REFERENCED +# found by runing print(dir(board)) on clue +A0 = 0 +A1 = 0 +A2 = 0 +A3 = 0 +A4 = 0 +A5 = 0 +A6 = 0 +A7 = 0 + +ACCELEROMETER_GYRO_INTERRUPT = 0 + +BUTTON_A = 0 +BUTTON_B = 0 + +D0 = 0 +D1 = 0 +D2 = 0 +D3 = 0 +D4 = 0 +D5 = 0 +D6 = 0 +D7 = 0 +D8 = 0 +D9 = 0 +D10 = 0 +D11 = 0 +D12 = 0 +D13 = 0 +D14 = 0 +D15 = 0 +D16 = 0 +D17 = 0 +D18 = 0 +D19 = 0 +D20 = 0 + +I2C = 0 + +L = 0 + +MICROPHONE_CLOCK = 0 +MICROPHONE_DATA = 0 + +MISO = 0 +MOSI = 0 + +P0 = 0 +P1 = 0 +P2 = 0 +P3 = 0 +P4 = 0 +P5 = 0 +P6 = 0 +P7 = 0 +P8 = 0 +P9 = 0 +P10 = 0 +P11 = 0 +P12 = 0 +P13 = 0 +P14 = 0 +P15 = 0 +P16 = 0 +P17 = 0 +P18 = 0 +P19 = 0 +P20 = 0 + +PROXIMITY_LIGHT_INTERRUPT = 0 + +RX = 0 +SCK = 0 +SCL = 0 +SDA = 0 + +SPEAKER = 0 + +SPI = 0 + +TFT_BACKLIGHT = 0 +TFT_CS = 0 +TFT_DC = 0 +TFT_MOSI = 0 +TFT_RESET = 0 +TFT_SCK = 0 + +TX = 0 + +UART = 0 + +WHITE_LEDS = 0 diff --git a/src/base_circuitpython/displayio/group.py b/src/base_circuitpython/displayio/group.py index 6dd44ddc2..7f316c487 100644 --- a/src/base_circuitpython/displayio/group.py +++ b/src/base_circuitpython/displayio/group.py @@ -24,6 +24,7 @@ def __init__(self, max_size, scale=1, check_active_group_ref=True, auto_write=Tr self.max_size = max_size self.scale = scale self.__parent = None + self.__hidden = False @property def in_group(self): @@ -31,12 +32,18 @@ def in_group(self): @property def hidden(self): - if self.__check_active_group_ref and board.DISPLAY.active_group == self: - return False - elif self.in_group: - return self.__parent.hidden - else: - return True + return self.__hidden + + @hidden.setter + def hidden(self, val): + changed = val != self.__hidden + + self.__hidden = val + for elem in self.__contents: + img = elem.hidden = val + + if changed: + self.__elem_changed() def append(self, item): if len(self.__contents) == self.max_size: @@ -156,10 +163,11 @@ def __draw(self, img=None, x=0, y=0, scale=None, show=True): pass for elem in self.__contents: - if isinstance(elem, Group): - img = elem._Group__draw(img=img, x=x, y=y, scale=scale, show=False,) - else: - img = elem._TileGrid__draw(img=img, x=x, y=y, scale=scale) + if not elem.hidden: + if isinstance(elem, Group): + img = elem._Group__draw(img=img, x=x, y=y, scale=scale, show=False,) + else: + img = elem._TileGrid__draw(img=img, x=x, y=y, scale=scale) # show should only be true to the highest parent group if show: diff --git a/src/base_circuitpython/displayio/tile_grid.py b/src/base_circuitpython/displayio/tile_grid.py index 5dfd747e9..d961b518a 100644 --- a/src/base_circuitpython/displayio/tile_grid.py +++ b/src/base_circuitpython/displayio/tile_grid.py @@ -2,6 +2,7 @@ from . import constants as CONSTANTS import threading import queue +from common import utils # TileGrid implementation loosely based on the # displayio.TileGrid class in Adafruit CircuitPython @@ -45,8 +46,9 @@ def __init__( self.bitmap = bitmap self.pixel_shader = pixel_shader self.default_tile = default_tile + self.hidden = False self.__parent = None - + # unimplemented features self.__flip_x = False self.__flip_y = False @@ -61,20 +63,13 @@ def in_group(self): self.__flip_y = False self.__transpose_xy = False - @property - def hidden(self): - if self.__parent == None: - return True - else: - return self.__parent.hidden - @property def flip_x(self): return self.__flip_x @flip_x.setter def flip_x(self, val): - print("feature not implemented") + utils.print_for_unimplemented_functions(TileGrid.flip_x.__name_) self.__flip_x = val @property @@ -83,7 +78,7 @@ def flip_y(self): @flip_y.setter def flip_y(self, val): - print("feature not implemented") + utils.print_for_unimplemented_functions(TileGrid.flip_y.__name_) self.__flip_y = val @property @@ -92,7 +87,7 @@ def transpose_xy(self): @transpose_xy.setter def transpose_xy(self, val): - print("feature not implemented") + utils.print_for_unimplemented_functions(TileGrid.transpose_xy.__name_) self.__transpose_xy = val # setitem for an index simply gets the index of the bitmap diff --git a/src/base_circuitpython/pulseio.py b/src/base_circuitpython/pulseio.py index f6632fb9e..f69a45836 100644 --- a/src/base_circuitpython/pulseio.py +++ b/src/base_circuitpython/pulseio.py @@ -1 +1,16 @@ -print("pulseio is implemented on the device but not on the simulator") +from common import utils + + +class PulseIn: + def __init__(self, pin, maxlen=2, *, idle_state=False): + utils.print_for_unimplemented_functions(PulseIn.__init__.__qualname__) + + +class PulseOut: + def __init__(self, carrier): + utils.print_for_unimplemented_functions(PulseOut.__init__.__qualname__) + + +class PWMOut: + def __init__(self, pin, *, duty_cycle=0, frequency=500, variable_frequency=False): + utils.print_for_unimplemented_functions(PWMOut.__init__.__qualname__) From b3b1e929828cd729e4af09c4a0c8c031c2a47c39 Mon Sep 17 00:00:00 2001 From: andreamah Date: Mon, 6 Apr 2020 12:01:30 -0700 Subject: [PATCH 04/11] added some docstrings --- src/base_circuitpython/displayio/group.py | 100 ++++++++---------- src/base_circuitpython/displayio/tile_grid.py | 50 +++++++-- 2 files changed, 88 insertions(+), 62 deletions(-) diff --git a/src/base_circuitpython/displayio/group.py b/src/base_circuitpython/displayio/group.py index 7f316c487..542ac3275 100644 --- a/src/base_circuitpython/displayio/group.py +++ b/src/base_circuitpython/displayio/group.py @@ -46,33 +46,13 @@ def hidden(self, val): self.__elem_changed() def append(self, item): - if len(self.__contents) == self.max_size: - raise RuntimeError(CONSTANTS.GROUP_FULL) - elif not isinstance(item, TileGrid) and not isinstance(item, Group): - raise ValueError(CONSTANTS.INCORR_SUBCLASS) - elif item.in_group: - raise ValueError(CONSTANTS.LAYER_ALREADY_IN_GROUP) - + self.__prepare_for_add(item) self.__contents.append(item) - if isinstance(item, TileGrid): - item._TileGrid__parent = self - else: - item._Group__parent = self self.__elem_changed() def insert(self, idx, item): - if len(self.__contents) == self.max_size: - raise RuntimeError(CONSTANTS.GROUP_FULL) - elif not isinstance(item, TileGrid) and not isinstance(item, Group): - raise ValueError(CONSTANTS.INCORR_SUBCLASS) - elif item.in_group: - raise ValueError(CONSTANTS.LAYER_ALREADY_IN_GROUP) - + self.__prepare_for_add(item) self.__contents.insert(idx, item) - if isinstance(item, TileGrid): - item._TileGrid__parent = self - else: - item._Group__parent = self self.__elem_changed() def index(self, layer): @@ -82,25 +62,59 @@ def index(self, layer): return ValueError() + def pop(self, i=-1): + item = self.__contents.pop(i) + self.__set_parent(item, None) + self.__elem_changed() + return item + def remove(self, layer): idx = self.index(layer) item = self.__contents[idx] - if isinstance(item, TileGrid): - item._TileGrid__parent = None - else: - item._Group__parent = None + + self.__set_parent(item, None) self.__contents.pop(idx) self.__elem_changed() def __delitem__(self, index): item = self.__contents[index] - if isinstance(item, TileGrid): - item._TileGrid__parent = None - else: - item._Group__parent = None + self.__set_parent(item, None) del self.__contents[index] self.__elem_changed() + def __getitem__(self, index): + return self.__contents[index] + + def __setitem__(self, index, val): + old_val = self.__contents[index] + + self.__contents[index] = val + if old_val != val: + self.__elem_changed() + + def __len__(self): + if not self.__contents: + return 0 + else: + return len(self.__contents) + + def __prepare_for_add(self, item): + if len(self.__contents) == self.max_size: + raise RuntimeError(CONSTANTS.GROUP_FULL) + elif not isinstance(item, TileGrid) and not isinstance(item, Group): + raise ValueError(CONSTANTS.INCORR_SUBCLASS) + elif (isinstance(item, Group) and item.in_group) or ( + isinstance(item, TileGrid) and item._TileGrid__in_group + ): + raise ValueError(CONSTANTS.LAYER_ALREADY_IN_GROUP) + self.__set_parent(item, self) + + def __set_parent(self, item, val): + if isinstance(item, TileGrid): + item._TileGrid__parent = val + else: + item._Group__parent = val + def __elem_changed(self): # Ensure that this group is what the board is currently showing. # Otherwise, don't bother to draw it. @@ -117,16 +131,6 @@ def __trigger_draw(self): # see if one of the parents are the current active group. self.__parent._Group__elem_changed() - def __getitem__(self, index): - return self.__contents[index] - - def __setitem__(self, index, val): - old_val = self.__contents[index] - - self.__contents[index] = val - if old_val != val: - self.__elem_changed() - def __draw(self, img=None, x=0, y=0, scale=None, show=True): # this function is not a part of the orignal implementation # it is what draws itself and its children and potentially shows it to the @@ -185,19 +189,3 @@ def __show(self, img): sendable_json = {CONSTANTS.BASE_64: img_str} common.utils.send_to_simulator(sendable_json, CONSTANTS.CLUE) - - def __len__(self): - if not self.__contents: - return 0 - else: - return len(self.__contents) - - def pop(self, i=-1): - item = self.__contents.pop(i) - - if isinstance(item, TileGrid): - item._TileGrid__parent = None - else: - item._Group__parent = None - self.__elem_changed() - return item diff --git a/src/base_circuitpython/displayio/tile_grid.py b/src/base_circuitpython/displayio/tile_grid.py index d961b518a..de94e1dc7 100644 --- a/src/base_circuitpython/displayio/tile_grid.py +++ b/src/base_circuitpython/displayio/tile_grid.py @@ -14,6 +14,34 @@ class TileGrid: + """ + :class:`TileGrid` -- A grid of tiles sourced out of one bitmap + ========================================================================== + + Position a grid of tiles sourced from a bitmap and pixel_shader combination. Multiple grids + can share bitmaps and pixel shaders. + + A single tile grid is also known as a Sprite. + + .. class:: TileGrid(bitmap, *, pixel_shader, width=1, height=1, tile_width=None, tile_height=None, default_tile=0, x=0, y=0) + + Create a TileGrid object. The bitmap is source for 2d pixels. The pixel_shader is used to + convert the value and its location to a display native pixel color. This may be a simple color + palette lookup, a gradient, a pattern or a color transformer. + + tile_width and tile_height match the height of the bitmap by default. + + :param displayio.Bitmap bitmap: The bitmap storing one or more tiles. + :param displayio.Palette pixel_shader: The pixel shader that produces colors from values + :param int width: Width of the grid in tiles. + :param int height: Height of the grid in tiles. + :param int tile_width: Width of a single tile in pixels. Defaults to the full Bitmap and must evenly divide into the Bitmap's dimensions. + :param int tile_height: Height of a single tile in pixels. Defaults to the full Bitmap and must evenly divide into the Bitmap's dimensions. + :param int default_tile: Default tile index to show. + :param int x: Initial x position of the left edge within the parent. + :param int y: Initial y position of the top edge within the parent. + """ + def __init__( self, bitmap, @@ -36,6 +64,14 @@ def __init__( else: self.tile_height = tile_height + self.x = None + """ + .. attribute:: x + + X position of the left edge in the parent. + """ + self.y = None + if position and isinstance(position, tuple): self.x = position[0] self.y = position[1] @@ -46,7 +82,14 @@ def __init__( self.bitmap = bitmap self.pixel_shader = pixel_shader self.default_tile = default_tile + self.hidden = False + """ + .. attribute:: hidden + + True when the TileGrid is hidden. This may be False even when a part of a hidden Group. + """ + self.__parent = None # unimplemented features @@ -55,14 +98,9 @@ def __init__( self.__transpose_xy = False @property - def in_group(self): + def __in_group(self): return self.__parent != None - # unimplemented features - self.__flip_x = False - self.__flip_y = False - self.__transpose_xy = False - @property def flip_x(self): return self.__flip_x From b457a0a15366803515708816d28be41789ee896c Mon Sep 17 00:00:00 2001 From: andreamah Date: Mon, 6 Apr 2020 15:55:28 -0700 Subject: [PATCH 05/11] more fixes and docstrings --- src/base_circuitpython/displayio/bitmap.py | 56 ++++++- src/base_circuitpython/displayio/group.py | 121 +++++++++++++-- src/base_circuitpython/displayio/palette.py | 56 ++++++- .../displayio/test/test_bitmap.py | 5 - .../displayio/test/test_palette.py | 3 - .../displayio/test/test_tile_grid.py | 18 +-- src/base_circuitpython/displayio/tile_grid.py | 139 ++++++++++++------ src/constants.ts | 4 +- src/extension.ts | 15 +- 9 files changed, 333 insertions(+), 84 deletions(-) diff --git a/src/base_circuitpython/displayio/bitmap.py b/src/base_circuitpython/displayio/bitmap.py index f995c8025..8b1f2b270 100644 --- a/src/base_circuitpython/displayio/bitmap.py +++ b/src/base_circuitpython/displayio/bitmap.py @@ -12,6 +12,25 @@ class Bitmap: + ''' + .. currentmodule:: displayio + + `Bitmap` -- Stores values in a 2D array + ========================================================================== + + Stores values of a certain size in a 2D array + + .. class:: Bitmap(width, height, value_count) + + Create a Bitmap object with the given fixed size. Each pixel stores a value that is used to + index into a corresponding palette. This enables differently colored sprites to share the + underlying Bitmap. value_count is used to minimize the memory used to store the Bitmap. + + :param int width: The number of values wide + :param int height: The number of values high + :param int value_count: The number of possible pixel values. + + ''' def __init__(self, width, height, value_count=255): self.__width = width self.__height = height @@ -19,13 +38,36 @@ def __init__(self, width, height, value_count=255): @property def width(self): + ''' + .. attribute:: width + + Width of the bitmap. (read only) + + ''' return self.__width @property def height(self): + ''' + .. attribute:: height + + Height of the bitmap. (read only) + + ''' return self.__height def __setitem__(self, index, value): + ''' + .. method:: __setitem__(index, value) + + Sets the value at the given index. The index can either be an x,y tuple or an int equal + to ``y * width + x``. + + This allows you to:: + + bitmap[0,1] = 3 + + ''' if isinstance(index, tuple): if index[0] >= self.width or index[1] >= self.height: raise IndexError(CONSTANTS.PIXEL_OUT_OF_BOUNDS) @@ -38,6 +80,17 @@ def __setitem__(self, index, value): raise IndexError(CONSTANTS.PIXEL_OUT_OF_BOUNDS) def __getitem__(self, index): + ''' + .. method:: __getitem__(index) + + Returns the value at the given index. The index can either be an x,y tuple or an int equal + to ``y * width + x``. + + This allows you to:: + + print(bitmap[0,1]) + + ''' if isinstance(index, tuple): if index[0] >= self.width or index[1] >= self.height: @@ -49,6 +102,3 @@ def __getitem__(self, index): return self.values[index] except IndexError: raise IndexError(CONSTANTS.PIXEL_OUT_OF_BOUNDS) - - def __len__(self): - return self.width * self.height diff --git a/src/base_circuitpython/displayio/group.py b/src/base_circuitpython/displayio/group.py index 542ac3275..1e3822d37 100644 --- a/src/base_circuitpython/displayio/group.py +++ b/src/base_circuitpython/displayio/group.py @@ -17,27 +17,64 @@ class Group: - def __init__(self, max_size, scale=1, check_active_group_ref=True, auto_write=True): + ''' + `Group` -- Group together sprites and subgroups + ========================================================================== + + Manage a group of sprites and groups and how they are inter-related. + + .. class:: Group(*, max_size=4, scale=1, x=0, y=0) + Create a Group of a given size and scale. Scale is in one dimension. For example, scale=2 + leads to a layer's pixel being 2x2 pixels when in the group. + :param int max_size: The maximum group size. + :param int scale: Scale of layer pixels in one dimension. + :param int x: Initial x position within the parent. + :param int y: Initial y position within the parent. + ''' + def __init__(self, max_size, scale=1, x=0,y=0,check_active_group_ref=True, auto_write=True): self.__check_active_group_ref = check_active_group_ref self.__auto_write = auto_write self.__contents = [] - self.max_size = max_size + self.__max_size = max_size self.scale = scale + ''' + .. attribute:: scale + + Scales each pixel within the Group in both directions. For example, when scale=2 each pixel + will be represented by 2x2 pixels. + + ''' + self.x = x + ''' + .. attribute:: x + + X position of the Group in the parent. + + ''' + self.y = y + ''' + .. attribute:: y + + Y position of the Group in the parent. + ''' self.__parent = None self.__hidden = False + - @property - def in_group(self): - return self.__parent != None @property def hidden(self): + ''' + .. attribute:: hidden + + True when the Group and all of it's layers are not visible. When False, the Group's layers + are visible if they haven't been hidden. + ''' return self.__hidden @hidden.setter def hidden(self, val): changed = val != self.__hidden - self.__hidden = val for elem in self.__contents: img = elem.hidden = val @@ -46,16 +83,31 @@ def hidden(self, val): self.__elem_changed() def append(self, item): + ''' + .. method:: append(layer) + + Append a layer to the group. It will be drawn above other layers. + ''' self.__prepare_for_add(item) self.__contents.append(item) self.__elem_changed() def insert(self, idx, item): + ''' + .. method:: insert(index, layer) + + Insert a layer into the group. + ''' self.__prepare_for_add(item) self.__contents.insert(idx, item) self.__elem_changed() def index(self, layer): + ''' + .. method:: index(layer) + + Returns the index of the first copy of layer. Raises ValueError if not found. + ''' for idx, elem in enumerate(self.__contents): if elem == layer: return idx @@ -63,12 +115,22 @@ def index(self, layer): return ValueError() def pop(self, i=-1): + ''' + .. method:: pop(i=-1) + + Remove the ith item and return it. + ''' item = self.__contents.pop(i) self.__set_parent(item, None) self.__elem_changed() return item def remove(self, layer): + ''' + .. method:: remove(layer) + + Remove the first copy of layer. Raises ValueError if it is not present. + ''' idx = self.index(layer) item = self.__contents[idx] @@ -77,15 +139,43 @@ def remove(self, layer): self.__elem_changed() def __delitem__(self, index): + ''' + .. method:: __delitem__(index) + + Deletes the value at the given index. + + This allows you to:: + + del group[0] + ''' item = self.__contents[index] self.__set_parent(item, None) del self.__contents[index] self.__elem_changed() def __getitem__(self, index): + ''' + .. method:: __getitem__(index) + + Returns the value at the given index. + + This allows you to:: + + print(group[0]) + + ''' return self.__contents[index] def __setitem__(self, index, val): + ''' + .. method:: __setitem__(index, value) + + Sets the value at the given index. + + This allows you to:: + + group[0] = sprite + ''' old_val = self.__contents[index] self.__contents[index] = val @@ -93,17 +183,26 @@ def __setitem__(self, index, val): self.__elem_changed() def __len__(self): + ''' + .. method:: __len__() + + Returns the number of layers in a Group + ''' if not self.__contents: return 0 else: return len(self.__contents) + @property + def __in_group(self): + return self.__parent != None + def __prepare_for_add(self, item): - if len(self.__contents) == self.max_size: + if len(self.__contents) == self.__max_size: raise RuntimeError(CONSTANTS.GROUP_FULL) elif not isinstance(item, TileGrid) and not isinstance(item, Group): raise ValueError(CONSTANTS.INCORR_SUBCLASS) - elif (isinstance(item, Group) and item.in_group) or ( + elif (isinstance(item, Group) and item._Group__in_group) or ( isinstance(item, TileGrid) and item._TileGrid__in_group ): raise ValueError(CONSTANTS.LAYER_ALREADY_IN_GROUP) @@ -126,7 +225,7 @@ def __trigger_draw(self): if self.__check_active_group_ref and board.DISPLAY.active_group == self: self.__draw() - elif self.in_group: + elif self.__in_group: # If a sub-group is modified, propagate to top level to # see if one of the parents are the current active group. self.__parent._Group__elem_changed() @@ -169,9 +268,9 @@ def __draw(self, img=None, x=0, y=0, scale=None, show=True): for elem in self.__contents: if not elem.hidden: if isinstance(elem, Group): - img = elem._Group__draw(img=img, x=x, y=y, scale=scale, show=False,) + img = elem._Group__draw(img=img, x=x+self.x, y=y+self.y, scale=scale, show=False,) else: - img = elem._TileGrid__draw(img=img, x=x, y=y, scale=scale) + img = elem._TileGrid__draw(img=img, x=x+self.x, y=y+self.y, scale=scale) # show should only be true to the highest parent group if show: diff --git a/src/base_circuitpython/displayio/palette.py b/src/base_circuitpython/displayio/palette.py index 878aee225..3519eceb1 100644 --- a/src/base_circuitpython/displayio/palette.py +++ b/src/base_circuitpython/displayio/palette.py @@ -9,30 +9,70 @@ class Palette: + ''' + `Palette` -- Stores a mapping from bitmap pixel palette_indexes to display colors + ========================================================================================= + + Map a pixel palette_index to a full color. Colors are transformed to the display's format internally to + save memory. + + .. class:: Palette(color_count) + + Create a Palette object to store a set number of colors. + + :param int color_count: The number of colors in the Palette + ''' def __init__(self, color_count): - self.color_count = color_count + self.__color_count = color_count self.__contents = [] # set all colours to black by default - for i in range(self.color_count): + for i in range(self.__color_count): self.__contents.append(_ColorType((0, 0, 0))) - def __getitem__(self, index): - if index >= self.color_count: - raise IndexError(CONSTANTS.PALETTE_OUT_OF_RANGE) - - return self.__contents[index].rgb888 def __setitem__(self, index, value): - if index >= self.color_count: + ''' + .. method:: __setitem__(index, value) + + Sets the pixel color at the given index. The index should be an integer in the range 0 to color_count-1. + + The value argument represents a color, and can be from 0x000000 to 0xFFFFFF (to represent an RGB value). + Value can be an int, bytes (3 bytes (RGB) or 4 bytes (RGB + pad byte)), bytearray, + or a tuple or list of 3 integers. + + This allows you to:: + + palette[0] = 0xFFFFFF # set using an integer + palette[1] = b'\xff\xff\x00' # set using 3 bytes + palette[2] = b'\xff\xff\x00\x00' # set using 4 bytes + palette[3] = bytearray(b'\x00\x00\xFF') # set using a bytearay of 3 or 4 bytes + palette[4] = (10, 20, 30) # set using a tuple of 3 integers + + ''' + if index >= self.__color_count: raise IndexError(CONSTANTS.PALETTE_OUT_OF_RANGE) self.__contents[index].rgb888 = value + def __len__(self): + ''' + .. method:: __len__() + + Returns the number of colors in a Palette + + ''' + return self.__color_count def make_transparent(self, index): + ''' + .. method:: make_transparent(palette_index) + ''' self.__toggle_transparency(index, True) def make_opaque(self, index): + ''' + .. method:: make_opaque(palette_index) + ''' self.__toggle_transparency(index, False) def __toggle_transparency(self, index, transparency): diff --git a/src/base_circuitpython/displayio/test/test_bitmap.py b/src/base_circuitpython/displayio/test/test_bitmap.py index 4691d4886..2967aa632 100644 --- a/src/base_circuitpython/displayio/test/test_bitmap.py +++ b/src/base_circuitpython/displayio/test/test_bitmap.py @@ -42,8 +42,3 @@ def test_get_set_index_err_singular_index(self, x_size, y_size, i): with pytest.raises(IndexError, match=CONSTANTS.PIXEL_OUT_OF_BOUNDS): val = bitmap[i] - - @pytest.mark.parametrize("x, y", [(1, 231), (342, 36), (0, 0)]) - def test_get_len(self, x, y): - bitmap = Bitmap(x, y) - assert len(bitmap) == x * y diff --git a/src/base_circuitpython/displayio/test/test_palette.py b/src/base_circuitpython/displayio/test/test_palette.py index bcf6b4284..e5ae09fac 100644 --- a/src/base_circuitpython/displayio/test/test_palette.py +++ b/src/base_circuitpython/displayio/test/test_palette.py @@ -20,9 +20,6 @@ def test_get_and_set_palette_err(self, palette_size, palette_index): with pytest.raises(IndexError, match=CONSTANTS.PALETTE_OUT_OF_RANGE): palette[palette_index] = 0 - with pytest.raises(IndexError, match=CONSTANTS.PALETTE_OUT_OF_RANGE): - pal = palette[palette_index] - def test_set_transparency(self): palette = Palette(5) assert palette._Palette__contents[2].transparent == False diff --git a/src/base_circuitpython/displayio/test/test_tile_grid.py b/src/base_circuitpython/displayio/test/test_tile_grid.py index b37a8048a..1f2d9d5fa 100644 --- a/src/base_circuitpython/displayio/test/test_tile_grid.py +++ b/src/base_circuitpython/displayio/test/test_tile_grid.py @@ -42,11 +42,11 @@ def test_basic_constructor( tile_grids = [tg1, tg2] for tg in tile_grids: - assert tg.bitmap.width == bitmap_w - assert tg.bitmap.height == bitmap_h + assert tg._TileGrid__bitmap.width == bitmap_w + assert tg._TileGrid__bitmap.height == bitmap_h assert len(tg.pixel_shader._Palette__contents) == palette_num - assert tg.tile_width == tile_width - assert tg.tile_height == tile_height + assert tg._TileGrid__tile_width == tile_width + assert tg._TileGrid__tile_height == tile_height assert tg.x == position[0] assert tg.y == position[1] @@ -58,11 +58,11 @@ def test_basic_constructor( y=position[1], ) - assert tg3.bitmap.width == bitmap_w - assert tg3.bitmap.height == bitmap_h + assert tg3._TileGrid__bitmap.width == bitmap_w + assert tg3._TileGrid__bitmap.height == bitmap_h assert len(tg3.pixel_shader._Palette__contents) == palette_num - assert tg3.tile_width == bitmap_w - assert tg3.tile_height == bitmap_h + assert tg3._TileGrid__tile_width == bitmap_w + assert tg3._TileGrid__tile_height == bitmap_h assert tg3.x == position[0] assert tg3.y == position[1] @@ -79,7 +79,7 @@ def test_tile_set_get(self, w, h, x, y): ) tg_x_y = tg[x, y] - assert tg_x_y == tg.bitmap[x, y] + assert tg_x_y == tg._TileGrid__bitmap[x, y] @pytest.mark.parametrize( "w, h, x, y", [(55, 56, 100, 1), (55, 56, 0, 56), (66, 88, 66, 88)] diff --git a/src/base_circuitpython/displayio/tile_grid.py b/src/base_circuitpython/displayio/tile_grid.py index de94e1dc7..d16b82088 100644 --- a/src/base_circuitpython/displayio/tile_grid.py +++ b/src/base_circuitpython/displayio/tile_grid.py @@ -15,7 +15,7 @@ class TileGrid: """ - :class:`TileGrid` -- A grid of tiles sourced out of one bitmap + `TileGrid` -- A grid of tiles sourced out of one bitmap ========================================================================== Position a grid of tiles sourced from a bitmap and pixel_shader combination. Multiple grids @@ -31,15 +31,15 @@ class TileGrid: tile_width and tile_height match the height of the bitmap by default. - :param displayio.Bitmap bitmap: The bitmap storing one or more tiles. - :param displayio.Palette pixel_shader: The pixel shader that produces colors from values - :param int width: Width of the grid in tiles. - :param int height: Height of the grid in tiles. - :param int tile_width: Width of a single tile in pixels. Defaults to the full Bitmap and must evenly divide into the Bitmap's dimensions. - :param int tile_height: Height of a single tile in pixels. Defaults to the full Bitmap and must evenly divide into the Bitmap's dimensions. - :param int default_tile: Default tile index to show. - :param int x: Initial x position of the left edge within the parent. - :param int y: Initial y position of the top edge within the parent. + :param displayio.Bitmap bitmap: The bitmap storing one or more tiles. + :param displayio.Palette pixel_shader: The pixel shader that produces colors from values + :param int width: Width of the grid in tiles. + :param int height: Height of the grid in tiles. + :param int tile_width: Width of a single tile in pixels. Defaults to the full Bitmap and must evenly divide into the Bitmap's dimensions. + :param int tile_height: Height of a single tile in pixels. Defaults to the full Bitmap and must evenly divide into the Bitmap's dimensions. + :param int default_tile: Default tile index to show. + :param int x: Initial x position of the left edge within the parent. + :param int y: Initial y position of the top edge within the parent. """ def __init__( @@ -53,43 +53,52 @@ def __init__( y=0, position=None, ): - - if tile_width is None: - self.tile_width = bitmap.width - else: - self.tile_width = tile_width - - if tile_height is None: - self.tile_height = bitmap.height - else: - self.tile_height = tile_height - self.x = None """ .. attribute:: x - + X position of the left edge in the parent. """ self.y = None + """ + .. attribute:: y - if position and isinstance(position, tuple): - self.x = position[0] - self.y = position[1] - else: - self.x = x - self.y = y - - self.bitmap = bitmap + Y position of the top edge in the parent. + """ self.pixel_shader = pixel_shader - self.default_tile = default_tile + """ + .. attribute:: pixel_shader + The pixel shader of the tilegrid. + """ self.hidden = False """ .. attribute:: hidden - True when the TileGrid is hidden. This may be False even when a part of a hidden Group. + True when the TileGrid is hidden. This may be False even when a part of a hidden Group. """ + self.__bitmap = bitmap + self.__tile_width = None + self.__tile_height = None + + if tile_width is None: + self.__tile_width = bitmap.width + else: + self.__tile_width = tile_width + + if tile_height is None: + self.__tile_height = bitmap.height + else: + self.__tile_height = tile_height + + if position and isinstance(position, tuple): + self.x = position[0] + self.y = position[1] + else: + self.x = x + self.y = y + self.__parent = None # unimplemented features @@ -97,12 +106,14 @@ def __init__( self.__flip_y = False self.__transpose_xy = False - @property - def __in_group(self): - return self.__parent != None @property def flip_x(self): + """ + .. attribute:: flip_x + + If true, the left edge rendered will be the right edge of the right-most tile. + """ return self.__flip_x @flip_x.setter @@ -112,6 +123,12 @@ def flip_x(self, val): @property def flip_y(self): + + """ + .. attribute:: flip_y + + If true, the top edge rendered will be the bottom edge of the bottom-most tile. + """ return self.__flip_y @flip_y.setter @@ -121,6 +138,13 @@ def flip_y(self, val): @property def transpose_xy(self): + + """ + .. attribute:: transpose_xy + + If true, the TileGrid's axis will be swapped. When combined with mirroring, any 90 degree + rotation can be achieved along with the corresponding mirrored version. + """ return self.__transpose_xy @transpose_xy.setter @@ -128,23 +152,50 @@ def transpose_xy(self, val): utils.print_for_unimplemented_functions(TileGrid.transpose_xy.__name_) self.__transpose_xy = val + # setitem for an index simply gets the index of the bitmap # rather than the tile index def __setitem__(self, index, value): + """ + .. method:: __setitem__(index, tile_index) + Sets the tile index at the given index. The index can either be an x,y tuple or an int equal + to ``y * width + x``. + + This allows you to:: + + grid[0] = 10 + + or:: + + grid[0,0] = 10 + """ if isinstance(index, tuple): - if index[0] >= self.tile_width or index[1] >= self.tile_height: + if index[0] >= self.__tile_width or index[1] >= self.__tile_height: raise IndexError(CONSTANTS.TILE_OUT_OF_BOUNDS) - self.bitmap[index] = value + self.__bitmap[index] = value # getitem for an index simply gets the index of the bitmap # rather than the tile index def __getitem__(self, index): + """ + .. method:: __getitem__(index) + Returns the tile index at the given index. The index can either be an x,y tuple or an int equal + to ``y * width + x``. + + This allows you to:: + + print(grid[0]) + """ if isinstance(index, tuple): - if index[0] >= self.tile_width or index[1] >= self.tile_height: + if index[0] >= self.__tile_width or index[1] >= self.__tile_height: raise IndexError(CONSTANTS.TILE_OUT_OF_BOUNDS) - return self.bitmap[index] + return self.__bitmap[index] + + @property + def __in_group(self): + return self.__parent != None # methods that are not in the origin class: @@ -155,7 +206,7 @@ def __draw(self, img, x, y, scale): y = self.y * scale + y new_shape = self.__draw_group( - x, y, 0, self.tile_height, 0, self.tile_width, scale + x, y, 0, self.__tile_height, 0, self.__tile_width, scale ) img.paste(new_shape, (x, y), new_shape) @@ -177,12 +228,14 @@ def __draw_group(self, x, y, y_start, y_end, x_start, x_end, scale): x_max = min(x_offset + scale, width * scale) y_max = min(y_offset + scale, height * scale) - curr_val = self.bitmap[j, i] - transparent = self.pixel_shader._Palette__contents[curr_val].transparent + curr_val = self.__bitmap[j, i] + palette_obj = self.pixel_shader._Palette__contents[curr_val] + + transparent = palette_obj.transparent if not transparent and x_offset >= 0 and y_offset >= 0: - curr_colour = self.pixel_shader[curr_val] + curr_colour = palette_obj.rgb888 self.__fill_pixel( curr_val, curr_colour, diff --git a/src/constants.ts b/src/constants.ts index 792735372..9ca39ef45 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -132,9 +132,11 @@ export const CONSTANTS = { ), }, FILESYSTEM: { - OUTPUT_DIRECTORY: "out", + BASE_CIRCUITPYTHON: "base_circuitpython", + CLUE: "clue", PYTHON_VENV_DIR: "venv", MICROPYTHON_DIRECTORY: "micropython", + OUTPUT_DIRECTORY: "out", }, INFO: { ALREADY_SUCCESSFUL_INSTALL: localize( diff --git a/src/extension.ts b/src/extension.ts index f691b7e5d..0992dfe47 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1011,6 +1011,8 @@ const updatePythonExtraPaths = () => { [ __dirname, path.join(__dirname, CONSTANTS.FILESYSTEM.MICROPYTHON_DIRECTORY), + path.join(__dirname, CONSTANTS.FILESYSTEM.CLUE), + path.join(__dirname, CONSTANTS.FILESYSTEM.BASE_CIRCUITPYTHON) ], vscode.ConfigurationTarget.Global ); @@ -1027,13 +1029,24 @@ const updatePylintArgs = (context: vscode.ExtensionContext) => { CONSTANTS.FILESYSTEM.MICROPYTHON_DIRECTORY ); + const cluePath: string = utils.createEscapedPath( + context.extensionPath, + CONSTANTS.FILESYSTEM.OUTPUT_DIRECTORY, + CONSTANTS.FILESYSTEM.CLUE + ); + + const baseCircuitPythonPath: string = utils.createEscapedPath( + context.extensionPath, + CONSTANTS.FILESYSTEM.OUTPUT_DIRECTORY, + CONSTANTS.FILESYSTEM.BASE_CIRCUITPYTHON + ); // update pylint args to extend system path // to include python libs local to extention updateConfigLists( "python.linting.pylintArgs", [ "--init-hook", - `import sys; sys.path.extend([\"${outPath}\",\"${micropythonPath}\"])`, + `import sys; sys.path.extend([\"${outPath}\",\"${micropythonPath}\",\"${cluePath}\",\"${baseCircuitPythonPath}\"])`, ], vscode.ConfigurationTarget.Workspace ); From 54eb26d4e742b5b81944b8bd8986ecba13e40fb9 Mon Sep 17 00:00:00 2001 From: andreamah Date: Mon, 6 Apr 2020 16:24:21 -0700 Subject: [PATCH 06/11] fixed group issue --- src/base_circuitpython/displayio/group.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/base_circuitpython/displayio/group.py b/src/base_circuitpython/displayio/group.py index 1e3822d37..27d8ab4ab 100644 --- a/src/base_circuitpython/displayio/group.py +++ b/src/base_circuitpython/displayio/group.py @@ -268,9 +268,9 @@ def __draw(self, img=None, x=0, y=0, scale=None, show=True): for elem in self.__contents: if not elem.hidden: if isinstance(elem, Group): - img = elem._Group__draw(img=img, x=x+self.x, y=y+self.y, scale=scale, show=False,) + img = elem._Group__draw(img=img, x=x, y=y, scale=scale, show=False,) else: - img = elem._TileGrid__draw(img=img, x=x+self.x, y=y+self.y, scale=scale) + img = elem._TileGrid__draw(img=img, x=x, y=y, scale=scale) # show should only be true to the highest parent group if show: From 040b91a2a7311ba286bb0d71c93859c3647dbc1f Mon Sep 17 00:00:00 2001 From: andreamah Date: Mon, 6 Apr 2020 16:25:42 -0700 Subject: [PATCH 07/11] fixed palette test --- src/base_circuitpython/displayio/test/test_palette.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/base_circuitpython/displayio/test/test_palette.py b/src/base_circuitpython/displayio/test/test_palette.py index e5ae09fac..8b0d1aa38 100644 --- a/src/base_circuitpython/displayio/test/test_palette.py +++ b/src/base_circuitpython/displayio/test/test_palette.py @@ -1,7 +1,7 @@ import pytest from ..palette import Palette from .. import constants as CONSTANTS -from ..color_type import ColorType +from ..color_type import _ColorType class TestPalette(object): @@ -12,7 +12,7 @@ class TestPalette(object): def test_get_and_set_palette(self, color_count, palette_num, val): palette = Palette(color_count) palette[palette_num] = val - assert palette._Palette__contents[palette_num] == ColorType(val) + assert palette._Palette__contents[palette_num] == _ColorType(val) @pytest.mark.parametrize("palette_size, palette_index", [(3, 7), (0, 0), (3, 3)]) def test_get_and_set_palette_err(self, palette_size, palette_index): From e1da686513b0f23371ea1a6c119560db5c1f0108 Mon Sep 17 00:00:00 2001 From: andreamah Date: Mon, 6 Apr 2020 16:36:32 -0700 Subject: [PATCH 08/11] formatting --- src/base_circuitpython/displayio/bitmap.py | 21 ++++--- src/base_circuitpython/displayio/group.py | 63 ++++++++++--------- src/base_circuitpython/displayio/palette.py | 23 +++---- src/base_circuitpython/displayio/tile_grid.py | 6 +- 4 files changed, 57 insertions(+), 56 deletions(-) diff --git a/src/base_circuitpython/displayio/bitmap.py b/src/base_circuitpython/displayio/bitmap.py index 8b1f2b270..c913dff2b 100644 --- a/src/base_circuitpython/displayio/bitmap.py +++ b/src/base_circuitpython/displayio/bitmap.py @@ -12,7 +12,7 @@ class Bitmap: - ''' + """ .. currentmodule:: displayio `Bitmap` -- Stores values in a 2D array @@ -30,7 +30,8 @@ class Bitmap: :param int height: The number of values high :param int value_count: The number of possible pixel values. - ''' + """ + def __init__(self, width, height, value_count=255): self.__width = width self.__height = height @@ -38,26 +39,26 @@ def __init__(self, width, height, value_count=255): @property def width(self): - ''' + """ .. attribute:: width Width of the bitmap. (read only) - ''' + """ return self.__width @property def height(self): - ''' + """ .. attribute:: height Height of the bitmap. (read only) - ''' + """ return self.__height def __setitem__(self, index, value): - ''' + """ .. method:: __setitem__(index, value) Sets the value at the given index. The index can either be an x,y tuple or an int equal @@ -67,7 +68,7 @@ def __setitem__(self, index, value): bitmap[0,1] = 3 - ''' + """ if isinstance(index, tuple): if index[0] >= self.width or index[1] >= self.height: raise IndexError(CONSTANTS.PIXEL_OUT_OF_BOUNDS) @@ -80,7 +81,7 @@ def __setitem__(self, index, value): raise IndexError(CONSTANTS.PIXEL_OUT_OF_BOUNDS) def __getitem__(self, index): - ''' + """ .. method:: __getitem__(index) Returns the value at the given index. The index can either be an x,y tuple or an int equal @@ -90,7 +91,7 @@ def __getitem__(self, index): print(bitmap[0,1]) - ''' + """ if isinstance(index, tuple): if index[0] >= self.width or index[1] >= self.height: diff --git a/src/base_circuitpython/displayio/group.py b/src/base_circuitpython/displayio/group.py index 27d8ab4ab..dfe2df9b8 100644 --- a/src/base_circuitpython/displayio/group.py +++ b/src/base_circuitpython/displayio/group.py @@ -17,7 +17,7 @@ class Group: - ''' + """ `Group` -- Group together sprites and subgroups ========================================================================== @@ -30,46 +30,47 @@ class Group: :param int scale: Scale of layer pixels in one dimension. :param int x: Initial x position within the parent. :param int y: Initial y position within the parent. - ''' - def __init__(self, max_size, scale=1, x=0,y=0,check_active_group_ref=True, auto_write=True): + """ + + def __init__( + self, max_size, scale=1, x=0, y=0, check_active_group_ref=True, auto_write=True + ): self.__check_active_group_ref = check_active_group_ref self.__auto_write = auto_write self.__contents = [] self.__max_size = max_size self.scale = scale - ''' + """ .. attribute:: scale Scales each pixel within the Group in both directions. For example, when scale=2 each pixel will be represented by 2x2 pixels. - ''' + """ self.x = x - ''' + """ .. attribute:: x X position of the Group in the parent. - ''' + """ self.y = y - ''' + """ .. attribute:: y Y position of the Group in the parent. - ''' + """ self.__parent = None self.__hidden = False - - @property def hidden(self): - ''' + """ .. attribute:: hidden True when the Group and all of it's layers are not visible. When False, the Group's layers are visible if they haven't been hidden. - ''' + """ return self.__hidden @hidden.setter @@ -83,31 +84,31 @@ def hidden(self, val): self.__elem_changed() def append(self, item): - ''' + """ .. method:: append(layer) Append a layer to the group. It will be drawn above other layers. - ''' + """ self.__prepare_for_add(item) self.__contents.append(item) self.__elem_changed() def insert(self, idx, item): - ''' + """ .. method:: insert(index, layer) Insert a layer into the group. - ''' + """ self.__prepare_for_add(item) self.__contents.insert(idx, item) self.__elem_changed() def index(self, layer): - ''' + """ .. method:: index(layer) Returns the index of the first copy of layer. Raises ValueError if not found. - ''' + """ for idx, elem in enumerate(self.__contents): if elem == layer: return idx @@ -115,22 +116,22 @@ def index(self, layer): return ValueError() def pop(self, i=-1): - ''' + """ .. method:: pop(i=-1) Remove the ith item and return it. - ''' + """ item = self.__contents.pop(i) self.__set_parent(item, None) self.__elem_changed() return item def remove(self, layer): - ''' + """ .. method:: remove(layer) Remove the first copy of layer. Raises ValueError if it is not present. - ''' + """ idx = self.index(layer) item = self.__contents[idx] @@ -139,7 +140,7 @@ def remove(self, layer): self.__elem_changed() def __delitem__(self, index): - ''' + """ .. method:: __delitem__(index) Deletes the value at the given index. @@ -147,14 +148,14 @@ def __delitem__(self, index): This allows you to:: del group[0] - ''' + """ item = self.__contents[index] self.__set_parent(item, None) del self.__contents[index] self.__elem_changed() def __getitem__(self, index): - ''' + """ .. method:: __getitem__(index) Returns the value at the given index. @@ -163,11 +164,11 @@ def __getitem__(self, index): print(group[0]) - ''' + """ return self.__contents[index] def __setitem__(self, index, val): - ''' + """ .. method:: __setitem__(index, value) Sets the value at the given index. @@ -175,7 +176,7 @@ def __setitem__(self, index, val): This allows you to:: group[0] = sprite - ''' + """ old_val = self.__contents[index] self.__contents[index] = val @@ -183,11 +184,11 @@ def __setitem__(self, index, val): self.__elem_changed() def __len__(self): - ''' + """ .. method:: __len__() Returns the number of layers in a Group - ''' + """ if not self.__contents: return 0 else: diff --git a/src/base_circuitpython/displayio/palette.py b/src/base_circuitpython/displayio/palette.py index 3519eceb1..b954d982b 100644 --- a/src/base_circuitpython/displayio/palette.py +++ b/src/base_circuitpython/displayio/palette.py @@ -9,7 +9,7 @@ class Palette: - ''' + """ `Palette` -- Stores a mapping from bitmap pixel palette_indexes to display colors ========================================================================================= @@ -21,7 +21,8 @@ class Palette: Create a Palette object to store a set number of colors. :param int color_count: The number of colors in the Palette - ''' + """ + def __init__(self, color_count): self.__color_count = color_count self.__contents = [] @@ -30,9 +31,8 @@ def __init__(self, color_count): for i in range(self.__color_count): self.__contents.append(_ColorType((0, 0, 0))) - def __setitem__(self, index, value): - ''' + """ .. method:: __setitem__(index, value) Sets the pixel color at the given index. The index should be an integer in the range 0 to color_count-1. @@ -49,30 +49,31 @@ def __setitem__(self, index, value): palette[3] = bytearray(b'\x00\x00\xFF') # set using a bytearay of 3 or 4 bytes palette[4] = (10, 20, 30) # set using a tuple of 3 integers - ''' + """ if index >= self.__color_count: raise IndexError(CONSTANTS.PALETTE_OUT_OF_RANGE) self.__contents[index].rgb888 = value + def __len__(self): - ''' + """ .. method:: __len__() Returns the number of colors in a Palette - ''' + """ return self.__color_count def make_transparent(self, index): - ''' + """ .. method:: make_transparent(palette_index) - ''' + """ self.__toggle_transparency(index, True) def make_opaque(self, index): - ''' + """ .. method:: make_opaque(palette_index) - ''' + """ self.__toggle_transparency(index, False) def __toggle_transparency(self, index, transparency): diff --git a/src/base_circuitpython/displayio/tile_grid.py b/src/base_circuitpython/displayio/tile_grid.py index d16b82088..730eb75e1 100644 --- a/src/base_circuitpython/displayio/tile_grid.py +++ b/src/base_circuitpython/displayio/tile_grid.py @@ -106,7 +106,6 @@ def __init__( self.__flip_y = False self.__transpose_xy = False - @property def flip_x(self): """ @@ -123,7 +122,7 @@ def flip_x(self, val): @property def flip_y(self): - + """ .. attribute:: flip_y @@ -138,7 +137,7 @@ def flip_y(self, val): @property def transpose_xy(self): - + """ .. attribute:: transpose_xy @@ -152,7 +151,6 @@ def transpose_xy(self, val): utils.print_for_unimplemented_functions(TileGrid.transpose_xy.__name_) self.__transpose_xy = val - # setitem for an index simply gets the index of the bitmap # rather than the tile index def __setitem__(self, index, value): From f6b752a63c423e2b21a9c55c6e20609765737adb Mon Sep 17 00:00:00 2001 From: andreamah Date: Mon, 6 Apr 2020 17:03:39 -0700 Subject: [PATCH 09/11] format ts --- src/extension.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/extension.ts b/src/extension.ts index e4bd1f833..13f0ce1d5 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1012,7 +1012,7 @@ const updatePythonExtraPaths = () => { __dirname, path.join(__dirname, CONSTANTS.FILESYSTEM.MICROPYTHON_DIRECTORY), path.join(__dirname, CONSTANTS.FILESYSTEM.CLUE), - path.join(__dirname, CONSTANTS.FILESYSTEM.BASE_CIRCUITPYTHON) + path.join(__dirname, CONSTANTS.FILESYSTEM.BASE_CIRCUITPYTHON), ], vscode.ConfigurationTarget.Global ); From 10dc42f5156c1908e5ee6dadff225869ab928186 Mon Sep 17 00:00:00 2001 From: andreamah Date: Tue, 7 Apr 2020 10:36:19 -0700 Subject: [PATCH 10/11] added correcting casing for CLUE --- src/process_user_code.py | 2 +- src/python_constants.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/process_user_code.py b/src/process_user_code.py index 1287fc241..b1d55554a 100644 --- a/src/process_user_code.py +++ b/src/process_user_code.py @@ -24,7 +24,7 @@ abs_path_to_parent_dir = os.path.dirname(os.path.abspath(__file__)) # Insert absolute path to library for CLUE into sys.path -sys.path.insert(0, os.path.join(abs_path_to_parent_dir, CONSTANTS.CLUE)) +sys.path.insert(0, os.path.join(abs_path_to_parent_dir, CONSTANTS.CLUE_DIR)) # Insert absolute path to Circuitpython libraries for CLUE into sys.path sys.path.insert(0, os.path.join(abs_path_to_parent_dir, CONSTANTS.CIRCUITPYTHON)) diff --git a/src/python_constants.py b/src/python_constants.py index 098e854f7..f469ec149 100644 --- a/src/python_constants.py +++ b/src/python_constants.py @@ -47,6 +47,7 @@ MICROBIT = "micro:bit" CLUE = "CLUE" +CLUE_DIR = "clue" CIRCUITPYTHON = "base_circuitpython" From d0e342acb67d47e5b834e7385104a5a984d1e04d Mon Sep 17 00:00:00 2001 From: andreamah Date: Tue, 7 Apr 2020 11:06:10 -0700 Subject: [PATCH 11/11] corrected img --- src/base_circuitpython/displayio/group.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/base_circuitpython/displayio/group.py b/src/base_circuitpython/displayio/group.py index dfe2df9b8..1f66b291d 100644 --- a/src/base_circuitpython/displayio/group.py +++ b/src/base_circuitpython/displayio/group.py @@ -78,7 +78,7 @@ def hidden(self, val): changed = val != self.__hidden self.__hidden = val for elem in self.__contents: - img = elem.hidden = val + elem.hidden = val if changed: self.__elem_changed()