diff --git a/mpf/config_players/segment_display_player.py b/mpf/config_players/segment_display_player.py index 9862e8a2c..7b5a18554 100644 --- a/mpf/config_players/segment_display_player.py +++ b/mpf/config_players/segment_display_player.py @@ -106,12 +106,22 @@ 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, value is bool(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/devices/segment_display/segment_display.py b/mpf/devices/segment_display/segment_display.py index 98a8c4344..01b8d9432 100644 --- a/mpf/devices/segment_display/segment_display.py +++ b/mpf/devices/segment_display/segment_display.py @@ -51,7 +51,8 @@ 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 +74,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 +90,16 @@ 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,6 +157,11 @@ 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() + + self._expand_colors(color, len(text)) + if self.config['update_method'] == "stack": self._text_stack[key] = TextStackEntry( text, color, flashing, flash_mask, transition, transition_out, priority, key) @@ -155,11 +172,43 @@ 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 + 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,13 +219,20 @@ 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 - if key in self._text_stack: - del self._text_stack[key] - self._update_stack() + self.remove_text_by_key(key) # pylint: disable=too-many-arguments def _start_transition(self, transition: TransitionBase, current_text: str, new_text: str, @@ -224,10 +280,11 @@ def _expand_colors(self, colors, length): """Expand color to a certain length.""" if not colors: colors = self._default_color - if len(colors) > length: - colors = colors[0:length] - elif len(colors) < length: - colors = colors + [colors[len(colors) - 1]] * (length - len(colors)) + else: + if (len(colors)) == 1: + colors = colors * length + elif len(colors) != length: + colors = [RGBColor("white")] * length return colors diff --git a/mpf/devices/segment_display/segment_display_text.py b/mpf/devices/segment_display/segment_display_text.py index 8ef8630c5..2b1419ac7 100644 --- a/mpf/devices/segment_display/segment_display_text.py +++ b/mpf/devices/segment_display/segment_display_text.py @@ -75,29 +75,35 @@ def _create_characters(cls, text: str, display_size: int, collapse_dots: bool, c 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, if text is shorter than display spaces add on the left + - 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) + # Adjust 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] + #0: char code 1: char_has_dot 2: char_has_comma + char_list.append(DisplayCharacter(char[0], char[1], char[2], color)) return char_list diff --git a/mpf/tests/test_SegmentDisplay.py b/mpf/tests/test_SegmentDisplay.py index 1fc928f83..a4632b9f3 100644 --- a/mpf/tests/test_SegmentDisplay.py +++ b/mpf/tests/test_SegmentDisplay.py @@ -476,8 +476,8 @@ def test_segment_display_text(self): self.assertEqual(5, len(test_text)) colors = test_text.get_colors() self.assertEqual(5, len(colors)) - self.assertEqual([RGBColor("white"), RGBColor("red"), RGBColor("red"), - RGBColor("red"), RGBColor("red")], colors) + self.assertEqual([RGBColor("white"), RGBColor("white"), RGBColor("white"), + RGBColor("white"), RGBColor("white")], colors) # multiple colors (fewer colors than letters and fewer letters than characters) test_text = SegmentDisplayText.from_str("COLOR", 8, False, False, False, @@ -487,7 +487,7 @@ def test_segment_display_text(self): colors = test_text.get_colors() self.assertEqual(8, len(colors)) self.assertEqual([RGBColor("white"), RGBColor("white"), RGBColor("white"), RGBColor("white"), - RGBColor("red"), RGBColor("red"), RGBColor("red"), RGBColor("red")], colors) + RGBColor("white"), RGBColor("white"), RGBColor("white"), RGBColor("white")], colors) def test_transitions(self): """Test segment display text transitions.""" @@ -1240,16 +1240,16 @@ def test_colors_and_transitions(self): self.advance_time_and_run(.5) self.assertEqual(" EVENT1", self.machine.segment_displays["display1"].text) self.assertEqual( - [RGBColor("red"), RGBColor("red"), RGBColor("red"), RGBColor("red"), RGBColor("red"), RGBColor("blue"), - RGBColor("yellow"), RGBColor("green"), RGBColor("white"), RGBColor("purple")], + [RGBColor("white"), RGBColor("white"), RGBColor("white"), RGBColor("white"), RGBColor("white"), RGBColor("white"), + RGBColor("white"), RGBColor("white"), RGBColor("white"), RGBColor("white")], self.machine.segment_displays["display1"].colors) self.post_event("test_event2") self.advance_time_and_run(2) self.assertEqual(" EVENT2", self.machine.segment_displays["display1"].text) self.assertEqual( - [RGBColor("red"), RGBColor("red"), RGBColor("red"), RGBColor("red"), RGBColor("red"), RGBColor("blue"), - RGBColor("yellow"), RGBColor("green"), RGBColor("white"), RGBColor("purple")], + [RGBColor("white"), RGBColor("white"), RGBColor("white"), RGBColor("white"), RGBColor("white"), RGBColor("white"), + RGBColor("white"), RGBColor("white"), RGBColor("white"), RGBColor("white")], self.machine.segment_displays["display1"].colors) @test_config("config_flashing.yaml") @@ -1362,12 +1362,12 @@ def test_text_stack(self): # remove "FIRST" entry display1.remove_text_by_key(None) self.assertEqual(" 3rd", display1.text) - self.assertEqual([RGBColor("blue")] * 10, display1.colors) + self.assertEqual([RGBColor("yellow")] * 10, display1.colors) self.assertEqual(FlashingType.NO_FLASH, display1.flashing) # set flashing display1.set_flashing(FlashingType.FLASH_MASK, "FFF ") - self.assertEqual([RGBColor("blue")] * 10, display1.colors) + self.assertEqual([RGBColor("yellow")] * 10, display1.colors) self.assertEqual(FlashingType.FLASH_MASK, display1.flashing) self.assertEqual("FFF ", display1.flash_mask) diff --git a/mpf/tests/test_VirtualSegmentDisplayConnector.py b/mpf/tests/test_VirtualSegmentDisplayConnector.py index 1c686fe00..c738440f7 100644 --- a/mpf/tests/test_VirtualSegmentDisplayConnector.py +++ b/mpf/tests/test_VirtualSegmentDisplayConnector.py @@ -45,7 +45,7 @@ def test_plugin(self, mock_bcp_trigger_client): self.assertIsNone(display3.virtual_connector) display1.add_text("NEW TEXT") - display1.set_color([RGBColor("FF0000"), RGBColor("00FF00")]) + display1.set_color([RGBColor("red"), RGBColor("lime"),RGBColor("red"), RGBColor("lime"),RGBColor("red"), RGBColor("lime"),RGBColor("red")]) self.assertTrue(mock_bcp_trigger_client.called) mock_bcp_trigger_client.assert_has_calls([call(client=ANY, flashing='False', flash_mask='', name='update_segment_display', segment_display_name='display1', @@ -54,8 +54,7 @@ def test_plugin(self, mock_bcp_trigger_client): call(client=ANY, name='update_segment_display', segment_display_name='display1', text='EW TEXT', flashing='False', flash_mask='', - colors=['ff0000', '00ff00','00ff00', '00ff00', '00ff00', - '00ff00', '00ff00'])]) + colors=['ff0000', '00ff00', 'ff0000', '00ff00', 'ff0000', '00ff00', 'ff0000'])]) mock_bcp_trigger_client.reset_mock() display2.add_text_entry("OTHER TEXT", [RGBColor("green")], FlashingType.FLASH_ALL, "", None, None, None, None)