diff --git a/mpf/config_players/segment_display_player.py b/mpf/config_players/segment_display_player.py index 9862e8a2c..a574a9b87 100644 --- a/mpf/config_players/segment_display_player.py +++ b/mpf/config_players/segment_display_player.py @@ -106,12 +106,23 @@ def _remove(self, instance_dict, key, display): del instance_dict[display][key] def clear_context(self, context): - """Remove all texts.""" - instance_dict = self._get_instance_dict(context) - for display, keys in instance_dict.items(): - for key in dict(keys).keys(): - self._remove(instance_dict=instance_dict, - key=key, display=display) + + ############## + # Remove all texts. Ignore what keys are available, that will be checked later in the segment display code. + # Especially important for update_method replace since there are no keys. + ############## + + instance_dict = self._get_instance_dict(context) # key of the dict is the display, the value is another dict + + for display, keys_dict in instance_dict.items(): # keys_dict key is the show key, the value is a boolean (with yet unknown usage) + if(keys_dict): #depending on the situation the keys_dict might be empty, still need to clear the display + for key in dict(keys_dict).keys(): + display.clear_segment_display(key) + if instance_dict[display][key] is not True: + self.delay.remove(instance_dict[display][key]) + del instance_dict[display][key] + else: + display.clear_segment_display(None) self._reset_instance_dict(context) diff --git a/mpf/config_spec.yaml b/mpf/config_spec.yaml index ae98b7742..c163a9054 100644 --- a/mpf/config_spec.yaml +++ b/mpf/config_spec.yaml @@ -1498,7 +1498,7 @@ segment_display_player: action: single|enum(add,remove,flash,no_flash,flash_match,flash_mask,set_color)|add transition: ignore transition_out: ignore - flashing: single|enum(off,all,match,mask,not_set)|not_set + flashing: single|enum(off,all,match,mask,not_set)|off flash_mask: single|str|None key: single|str|None expire: single|ms_or_token|None diff --git a/mpf/devices/segment_display/segment_display.py b/mpf/devices/segment_display/segment_display.py index 98a8c4344..1c4371d42 100644 --- a/mpf/devices/segment_display/segment_display.py +++ b/mpf/devices/segment_display/segment_display.py @@ -51,7 +51,7 @@ class SegmentDisplay(SystemWideDevice): __slots__ = ["hw_display", "size", "virtual_connector", "_text_stack", "_current_placeholder", "_current_text_stack_entry", "_transition_update_task", "_current_transition", "_default_color", - "_current_state", "_current_placeholder_future"] + "_current_state", "_current_placeholder_future", "_previous_text", "_previous_color", "_previous_transition_out"] config_section = 'segment_displays' collection = 'segment_displays' @@ -73,6 +73,9 @@ def __init__(self, machine, name: str) -> None: self._current_transition = None # type: Optional[TransitionRunner] self._default_color = None # type: Optional[RGBColor] self._current_state = None # type: Optional[SegmentDisplayState] + self._previous_text = None # Last text entry for transitions if update_method = replace + self._previous_color = None # Last color for transitions if update_method = replace + self._previous_transition_out = None # Last transistion_out if update_method = replace async def _initialize(self): """Initialize display.""" @@ -86,8 +89,14 @@ async def _initialize(self): self.size = self.config['size'] self._default_color = [RGBColor(color) for color in self.config["default_color"][0:self.size]] - if len(self._default_color) < self.size: - self._default_color += [RGBColor("white")] * (self.size - len(self._default_color)) + + if (len(self._default_color)) == 1: + self._default_color = self._default_color * self.size + elif len(self._default_color) != self.size: + self.warning_log("The amount of colors you specified for your text has to be either equal to " + "the amount of digits in your display or equals 1. Your display has a size of %s and the " + "amount of colors specified is %s. All display colors will be set to white.", self.size, len(self._default_color)) + self._default_color = [RGBColor("white")] * self.size # configure hardware try: @@ -145,7 +154,12 @@ def add_text_entry(self, text, color, flashing, flash_mask, transition, transiti This will replace texts with the same key. """ + if not color: + color = self._current_state.text.get_colors() + if self.config['update_method'] == "stack": + + self._text_stack[key] = TextStackEntry( text, color, flashing, flash_mask, transition, transition_out, priority, key) self._update_stack() @@ -155,11 +169,46 @@ def add_text_entry(self, text, color, flashing, flash_mask, transition, transiti raise ValueError(f"Unknown update_method '{self.config['update_method']}' for segment display {self.name}") # For the replace-text update method, skip the stack and write straight to the display - new_text = TextTemplate(self.machine, text).evaluate({}) - text = SegmentDisplayText.from_str(new_text, self.size, self.config['integrated_dots'], - self.config['integrated_commas'], self.config['use_dots_for_commas'], - color) - self._update_display(SegmentDisplayState(text, flashing, flash_mask)) + + ############################### + #Store current color and text as previous text/color of next run even if no transition in this step, + #the next step might have a transition, that the old text/color needs to be included into that transition + ############################### + + # Handle new and previous text + previous_text = self._previous_text or "" + self._previous_text = text # Save the new text as the next previous text + + # Handle new and previous color + previous_color = self._previous_color or self._default_color + self._previous_color = color # Save the new color as the next previous color + + if transition or self._previous_transition_out: + transition_conf = TransitionManager.get_transition(self.size, + self.config['integrated_dots'], + self.config['integrated_commas'], + self.config['use_dots_for_commas'], + transition or self._previous_transition_out) + + #start transition + self._start_transition(transition_conf, previous_text, text, + previous_color, color, + self.config['default_transition_update_hz'], flashing, flash_mask) + + else: #No transition configured + if transition_out: #in case transition_out is set we need to preserve it for the next step + self._previous_transition_out = transition_out + new_text = TextTemplate(self.machine, text).evaluate({}) + text = SegmentDisplayText.from_str(new_text, self.size, self.config['integrated_dots'], + self.config['integrated_commas'], self.config['use_dots_for_commas'], + color) + self._update_display(SegmentDisplayState(text, flashing, flash_mask)) + + ############################### + # Once the transistion_out is played, removed it that is not played in the next step again, but in case transition_out is set in the current step + # then we need to preserve it for the next step but only after the previous step's transition_out is in this step's config (or the transition of the current step) + ############################### + self._previous_transition_out = transition_out or None def add_text(self, text: str, priority: int = 0, key: str = None) -> None: """Add text to display stack. @@ -170,21 +219,30 @@ def add_text(self, text: str, priority: int = 0, key: str = None) -> None: def remove_text_by_key(self, key: Optional[str]): """Remove entry from text stack.""" - if self.config['update_method'] != "stack": - self.info_log("Segment display 'remove' action is TBD.") - return + if self.config['update_method'] == "stack": + if key in self._text_stack: + del self._text_stack[key] + self._update_stack() + else: # must be update_method replace, send empyt text since no key in that case + self.add_text_entry("", self._previous_color, FlashingType.NO_FLASH, "", None, None, 100, key) + + + def clear_segment_display(self, key: Optional[str]): + """Clear segment dispaly if context is removed from player.""" + + if self.config['update_method'] == "replace": + self._stop_transition() + self._previous_transition_out = None + + self.remove_text_by_key(key) - if key in self._text_stack: - del self._text_stack[key] - self._update_stack() # pylint: disable=too-many-arguments def _start_transition(self, transition: TransitionBase, current_text: str, new_text: str, current_colors: List[RGBColor], new_colors: List[RGBColor], update_hz: float, flashing, flash_mask): + """Start the specified transition.""" - current_colors = self._expand_colors(current_colors, len(current_text)) - new_colors = self._expand_colors(new_colors, len(new_text)) if self._current_transition: self._stop_transition() self._current_transition = TransitionRunner(self.machine, transition, current_text, new_text, diff --git a/mpf/devices/segment_display/segment_display_text.py b/mpf/devices/segment_display/segment_display_text.py index 8ef8630c5..60b28dc64 100644 --- a/mpf/devices/segment_display/segment_display_text.py +++ b/mpf/devices/segment_display/segment_display_text.py @@ -74,30 +74,32 @@ def _embed_dots_and_commas(cls, text: str, collapse_dots: bool, collapse_commas: def _create_characters(cls, text: str, display_size: int, collapse_dots: bool, collapse_commas: bool, use_dots_for_commas: bool, colors: List[Optional[RGBColor]]) -> List[DisplayCharacter]: """Create characters from text and color them. - - - Colors are used from the left to the right (starting with the first character). - - If colors are shorter than text the last color is repeated for text. - - The first color is used to pad the text to the left if text is shorter than the display - thus text is right - aligned. - Dots and commas are embedded on the fly. + - Text will be right aligned on the display, thus if text is shorter than display spaces will be padded before the text + - If list of colors is less than the display size then all white will be used, if only one color is given that will be used for the full display """ char_list = [] - left_pad_color = colors[0] if colors else None - default_right_color = colors[len(colors) - 1] if colors else None uncolored_chars = cls._embed_dots_and_commas(text, collapse_dots, collapse_commas, use_dots_for_commas) - colors = colors[-len(uncolored_chars):] - for char_code, char_has_dot, char_has_comma in uncolored_chars: - color = colors.pop(0) if colors else default_right_color - char_list.append(DisplayCharacter(char_code, char_has_dot, char_has_comma, color)) - # ensure list is the same size as the segment display (cut off on left or right justify characters) - current_length = len(char_list) + # Adujust the color array if needed + if (len(colors)) == 1: + colors = colors * display_size + elif len(colors) != display_size: + #TODO: Log that colors were adjusted to white as default + colors = [RGBColor("white")] * display_size + + # ensure list is the same size as the segment display (cut off on left if too long or right justify characters if too short) + current_length = len(uncolored_chars) if current_length > display_size: for _ in range(current_length - display_size): - char_list.pop(0) + uncolored_chars.pop(0) # remove very left char of array if too long elif current_length < display_size: for _ in range(display_size - current_length): - char_list.insert(0, DisplayCharacter(SPACE_CODE, False, False, left_pad_color)) + uncolored_chars.insert(0, (SPACE_CODE, False, False)) + + for i, char in enumerate(uncolored_chars): + color = colors[i] + char_list.append(DisplayCharacter(char[0], char[1], char[2], color)) #0: char code 1: char_has_dot 2: char_has_comma return char_list