adafruit/Adafruit_CircuitPython_Display_Text

bitmap_label losing direction

TerryCornall opened this issue ยท 6 comments

Hi!
I'm using adafruit-circuitpython-bundle-7.x-mpy-20220507.zip with Circuitpython adafruit-circuitpython-feather_m4_express-en_US-7.2.5.uf2
It's running on an Adafruit feather m4 express with a 2.7" Adafruit Sharp Memory display. (Lovely rig, BTW works very well other than this little issue)

I noticed an issue with adafruit_display_text.bitmap_label just recently where despite the label_direction being set to 'UPR' the label would reset to 'LTR'
After chasing thru my code and making sure I wasn't resetting it to LTR myself, I tried using adafruit_display_text.label rather than a bitmap_label and the problem went away.
Here's a cut-down version (original is waaaaay too big) of the code and it exhibits the same issues.
At first, it appears to work and the label is indeed upwards but if I tell it again that its label_direction is 'UPR' (see down bottom of code marked by ####### )
it suddenly goes LTR orientation and stays that way, despite reporting UPR. It doesn't matter if you do this every cycle or just once, it stays broken.

If I switch to using adafruit_display_text.label rather than a bitmap_label the problem doesn't happen.
Thanks!

from adafruit_display_text.bitmap_label import Label #use this instead of adafruit_display_text.label.Label
#from adafruit_display_text.label import Label  #no, use this because bitmap_label loses orientation sometimes
import time
import sharpdisplay
import displayio
import framebufferio
import board
from adafruit_bitmap_font import bitmap_font

class sharp_display():
    def __init__(self):
        self.spi = board.SPI()
        self.disp_cs_pin = board.D4  #D4 is on same side as the SCK, MO, 3,3V, etc so use that
        self.disp_w = 400
        self.disp_h = 240
        self.disp_ncolrs = 2
        self.rotation = 180
        self.framebuffer = sharpdisplay.SharpMemoryFramebuffer(self.spi, self.disp_cs_pin, self.disp_w, self.disp_h)
        self.display = framebufferio.FramebufferDisplay(self.framebuffer, rotation=self.rotation,
                auto_refresh=True)
        self.font = bitmap_font.load_font("fonts/LeagueSpartan-Bold-16.bdf") #big, use scale 1 or 2
        self.palette = displayio.Palette(2)
        self.palette[0] = 0xFFFFFF #so a color of FFFFFF puts a 0 makes display show black
        self.palette[1] = 0x000000 #A value of 0 puts 1 into bitmap. White
        self.page0 = displayio.Group()
        self.bmp0 = displayio.Bitmap(self.disp_w, self.disp_h, self.disp_ncolrs)
        self.tg0 = displayio.TileGrid(self.bmp0,pixel_shader=self.palette) #need a tilegrid for bmp0
        self.labelStatus = Label(font=self.font, background_tight=True, padding_left=0, padding_top =0,
                    text="DON'T PANIC",
                    color =0, line_spacing=0.7,
                    background_color=0xFFFFFF, #color, not palette index.
                    label_direction = 'UPR', #oooh, a direction!
                    scale=1,
                    anchored_position=(0, self.display.height),
                    anchor_point=(0,1), #bottom left because of the UPR direction
                    #LTR-Left-To-Right RTL-Right-To-Left TTB-Top-To-Bottom UPR-Upwards DWR-Downwards.
                    # It defaults to LTR
                    save_text=True #if you don't make this true it gives grief when changing scale or direction?
                    )
        self.page0.append(self.tg0) #attach the tilegroup with bitmap 0 to page0
        self.page0.append(self.labelStatus) #
        self.display.show(self.page0) #show me
       
        ############## here's the problem
        self.labelStatus.label_direction = 'UPR'  #Telling it again what it's dir is apparently breaks it

#main code
displayio.release_displays() #Do this to get display away from CircuitPython
app = sharp_display()
print('Testing bitmap_label')
first_time = True
while True:
    time.sleep(1)           #at first the label is upwards (UPR)
    app.labelStatus.text='OK, PANIC'  # but when I change the text it becomes LTR
    time.sleep(1)
    while True:  #stop Circuitpython stealing the display back
        time.sleep(1)

Thanks for reporting this and sharing the pared down reproducer code.

My guess is we have somewhere inside of bitmap_label where it's not correctly checking and using the direction setting. I'll look into this over the upcoming weekend and see if I can pin it down if no one else gets to it sooner.

No worries. Thanks for the great work!

Hello! ๐Ÿˆโ€โฌ› ๐Ÿˆโ€โฌ›

If a fix is still needed for this bug, I think I found the problem.

The class in bitmap_label is saving 2 values, the current _label_direction, and the previous _prev_label_direction,
probably for avoiding too many text manipulations, if unnecessary.

The problem is when someone sets the label_direction to the same value twice in a row, like in the code above:

   self.labelStatus = Label(font=self.font, background_tight=True, padding_left=0, padding_top =0,
                     ......
                     label_direction = 'UPR', #oooh, a direction!
                     ......
 
   ############## here's the problem
   self.labelStatus.label_direction = 'UPR'  #Telling it again what it's dir is apparently breaks it

_label_direction and _prev_label_direction will have the same value.

That means when the text value is changed, and the _reset_text() function is called, this portion of the code (lines 234-239) will do nothing, and the label will get the defaul orientation (LTR).

            # Set TileGrid properties based on label_direction
            if self._label_direction != self._prev_label_direction:
                tg1 = self._tilegrid
                tg1.transpose_xy, tg1.flip_x, tg1.flip_y = self._DIR_MAP[
                    self._label_direction
                ]

I reproduced this bug on both Pyportal Titano, and the PyGamer boards. I could only do a fix on the PyGamer though, for the Titano I would have had to recompile CircuitPython without the frozen module ๐Ÿ˜

The fix I found was to add a check in the _set_label_direction function (line 549), and only change the class values if the new one is different.

    def _set_label_direction(self, new_label_direction: str) -> None:
        if self._label_direction != new_label_direction:
            self._prev_label_direction = self._label_direction
            self._label_direction = new_label_direction
            self._reset_text(text=str(self._text))  # Force a recalculation

Please let me know if this looks right to you! ๐Ÿฑ @FoamyGuy

@snkYmkrct Thanks for looking further into this and submitting the fix!! I'll try it out today.

I could only do a fix on the PyGamer though, for the Titano I would have had to recompile CircuitPython without the frozen module

There is A nifty trick for this type of situation where you want to test a library that is frozen in to a build. If you put the library files in the root of the CIRCUITPY drive it will override the frozen in one allowing you to test your custom one. So the structure would be:

CIRCUITPY/adafruit_display_text/

instead of the more normal:

CIRCUITPY/lib/adafruit_display_text/

The one in the root gets precedence, even over the frozen in library if there is one.

resolved by #174

@FoamyGuy

If you put the library files in the root of the CIRCUITPY drive it will override the frozen in one allowing you to test your custom one.

I did not know that! Thank you for the tip, and for merging my PR! ๐Ÿ™‚