2727__version__ = "0.0.0+auto.0"
2828__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Display_Text.git"
2929
30+ import adafruit_ticks
3031import displayio
3132from micropython import const
3233
@@ -66,10 +67,16 @@ class Label(LabelBase):
6667 glyph (if its one line), or the (number of lines * linespacing + M)/2. That is,
6768 it will try to have it be center-left as close as possible.
6869
70+ Optionally supports:
71+ - Accented ranges of text with different colors
72+ - Outline stroke around the text
73+ - Fixed-width scrolling "marquee"
74+
6975 :param font: A font class that has ``get_bounding_box`` and ``get_glyph``.
7076 Must include a capital M for measuring character size.
7177 :type font: ~fontio.FontProtocol
72- :param str text: Text to display
78+ :param str text: The full text to show in the label. If this is longer than
79+ ``max_characters`` then the label will scroll to show everything.
7380 :param int|Tuple(int, int, int) color: Color of all text in HEX or RGB
7481 :param int|Tuple(int, int, int)|None background_color: Color of the background, use `None`
7582 for transparent
@@ -97,6 +104,20 @@ class Label(LabelBase):
97104 configurations possibles ``LTR``-Left-To-Right ``RTL``-Right-To-Left
98105 ``UPD``-Upside Down ``UPR``-Upwards ``DWR``-Downwards. It defaults to ``LTR``
99106 :param bool verbose: print debugging information in some internal functions. Default to False
107+ :param Optional[Union[Tuple, int]] outline_color: The color of the outline stroke
108+ as RGB tuple, or hex.
109+ :param int outline_size: The size in pixels of the outline stroke.
110+ Defaults to 1 pixel.
111+ :param Optional[displayio.Palette] color_palette: The palette to use for the Label.
112+ Indexes 0, 1, and 2 will be filled with background, foreground, and outline colors
113+ automatically. Indexes 3 and above can be used for accent colors.
114+ :param Optional[int] max_characters: The number of characters that sets the fixed-width.
115+ Default is None for unlimited width and no scrolling
116+
117+ :param float animate_time: The number of seconds in between scrolling animation
118+ frames. Default is 0.3 seconds.
119+ :param int current_index: The index of the first visible character in the label.
120+ Default is 0, the first character. Will increase while scrolling.
100121
101122 """
102123
@@ -116,6 +137,9 @@ def __init__(
116137 color_palette : Optional [displayio .Palette ] = None ,
117138 outline_color : Optional [int ] = None ,
118139 outline_size : int = 1 ,
140+ max_characters : Optional [int ] = None ,
141+ animate_time : float = 0.3 ,
142+ current_index : int = 0 ,
119143 ** kwargs ,
120144 ) -> None :
121145 self ._bitmap = None
@@ -133,6 +157,11 @@ def __init__(
133157
134158 super ().__init__ (font , ** kwargs )
135159
160+ self .animate_time = animate_time
161+ self ._current_index = current_index
162+ self ._last_animate_time = - 1
163+ self ._max_characters = max_characters
164+
136165 if color_palette is not None :
137166 if len (color_palette ) <= 3 :
138167 raise ValueError (
@@ -168,13 +197,16 @@ def __init__(
168197 self ._save_text = save_text
169198 self ._text = self ._replace_tabs (self ._text )
170199
171- # call the text updater with all the arguments.
172- self ._reset_text (
173- font = font ,
174- text = self ._text ,
175- line_spacing = self ._line_spacing ,
176- scale = self .scale ,
177- )
200+ if "text" in kwargs :
201+ text = kwargs ["text" ]
202+ else :
203+ text = ""
204+ if self ._max_characters is not None :
205+ if text and text [- 1 ] != " " and len (text ) > max_characters :
206+ text = f"{ text } "
207+ self ._full_text = text
208+
209+ self .update (True )
178210
179211 def _init_outline_stamp (self , outline_size ):
180212 self ._outline_size = outline_size
@@ -687,6 +719,107 @@ def bitmap(self) -> displayio.Bitmap:
687719 """
688720 return self ._bitmap
689721
722+ def update (self , force : bool = False ) -> None :
723+ """Attempt to update the display. If ``animate_time`` has elapsed since
724+ previews animation frame then move the characters over by 1 index.
725+ Must be called in the main loop of user code.
726+
727+ :param bool force: whether to ignore ``animation_time`` and force the update.
728+ Default is False.
729+ :return: None
730+ """
731+ _now = adafruit_ticks .ticks_ms ()
732+ if force or adafruit_ticks .ticks_less (
733+ self ._last_animate_time + int (self .animate_time * 1000 ), _now
734+ ):
735+ if self ._max_characters is None :
736+ self ._set_text (self ._full_text , self .scale )
737+ self ._last_animate_time = _now
738+ return
739+
740+ if len (self .full_text ) <= self .max_characters :
741+ if self ._text != self .full_text :
742+ self ._set_text (self .full_text , self .scale )
743+ self ._last_animate_time = _now
744+ return
745+
746+ if self .current_index + self .max_characters <= len (self .full_text ):
747+ _showing_string = self .full_text [
748+ self .current_index : self .current_index + self .max_characters
749+ ]
750+ else :
751+ _showing_string_start = self .full_text [self .current_index :]
752+ _showing_string_end = "{}" .format (
753+ self .full_text [
754+ : (self .current_index + self .max_characters ) % len (self .full_text )
755+ ]
756+ )
757+
758+ _showing_string = f"{ _showing_string_start } { _showing_string_end } "
759+ self ._set_text (_showing_string , self .scale )
760+ self .current_index += 1
761+ self ._last_animate_time = _now
762+
763+ return
764+
765+ @property
766+ def current_index (self ) -> int :
767+ """Index of the first visible character.
768+
769+ :return int: The current index
770+ """
771+ return self ._current_index
772+
773+ @current_index .setter
774+ def current_index (self , new_index : int ) -> None :
775+ if self .full_text :
776+ self ._current_index = new_index % len (self .full_text )
777+ else :
778+ self ._current_index = 0
779+
780+ @property
781+ def full_text (self ) -> str :
782+ """The full text to be shown. If it's longer than ``max_characters`` then
783+ scrolling will occur as needed.
784+
785+ :return str: The full text of this label.
786+ """
787+ return self ._full_text
788+
789+ @full_text .setter
790+ def full_text (self , new_text : str ) -> None :
791+ """
792+ User code should use the ``text`` property instead of this.
793+ """
794+ if self ._max_characters is not None :
795+ if new_text and new_text [- 1 ] != " " and len (new_text ) > self .max_characters :
796+ new_text = f"{ new_text } "
797+ if new_text != self ._full_text :
798+ self ._full_text = new_text
799+ self .current_index = 0
800+ self .update (True )
801+ else :
802+ self ._full_text = new_text
803+
804+ @property
805+ def max_characters (self ):
806+ """The maximum number of characters to display on screen.
807+
808+ :return int: The maximum character length of this label.
809+ """
810+ return self ._max_characters
811+
812+ @max_characters .setter
813+ def max_characters (self , new_max_characters ):
814+ """Recalculate the full text based on the new max characters.
815+
816+ This is necessary to correctly handle the potential space at the end of
817+ the text.
818+ """
819+ if new_max_characters != self ._max_characters :
820+ self ._max_characters = new_max_characters
821+ self .full_text = self .full_text
822+
690823 @property
691824 def outline_color (self ):
692825 """Color of the outline to draw around the text. Or None for no outline."""
@@ -806,3 +939,17 @@ def clear_accent_ranges(self):
806939 """
807940 self ._accent_ranges = []
808941 self ._reset_text (text = str (self ._text ))
942+
943+ @property
944+ def text (self ):
945+ """The full text to be shown. If it's longer than ``max_characters`` then
946+ scrolling will occur as needed.
947+
948+ :return str: The full text of this label.
949+ """
950+ return self .full_text
951+
952+ @text .setter
953+ def text (self , new_text ):
954+ self .full_text = new_text
955+ self .update (True )
0 commit comments