-
Notifications
You must be signed in to change notification settings - Fork 143
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Initial Implementation for display_segment update_method replace #1820
base: dev
Are you sure you want to change the base?
Changes from 4 commits
8138d2d
0cb6e3c
06b04cb
ae93e49
7dd058f
875beea
f240263
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -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.""" | ||||
|
@@ -145,7 +148,14 @@ def add_text_entry(self, text, color, flashing, flash_mask, transition, transiti | |||
|
||||
This will replace texts with the same key. | ||||
""" | ||||
|
||||
if len(color) == 0: | ||||
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 +165,56 @@ 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 | ||||
if self._previous_text: | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For pythonic convenience, this could be rewritten as:
Same below with |
||||
previous_text = self._previous_text | ||||
else: | ||||
previous_text = "" | ||||
self._previous_text = text # Save the new text as the next previous text | ||||
|
||||
# Handle new and previous color | ||||
if self._previous_color: | ||||
previous_color = self._previous_color | ||||
else: | ||||
previous_color = self._default_color | ||||
self._previous_color = color # Save the new color as the next previous color | ||||
|
||||
if transition or self._previous_transition_out: | ||||
if transition: #if transition exists, then ignore transition_out of previous text/color | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This second nested block could be removed by passing both possible values as arguments.
|
||||
transition_conf = TransitionManager.get_transition(self.size, | ||||
self.config['integrated_dots'], | ||||
self.config['integrated_commas'], | ||||
self.config['use_dots_for_commas'], | ||||
transition) | ||||
elif 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'], | ||||
self._previous_transition_out) | ||||
self._previous_transition_out = None # Once the transistion_out is played removed it that is not played in the next step again | ||||
if transition_out: #in case transition_out is set we need to preserve it for the next step but only after the previous transition_out is in this step's config | ||||
self._previous_transition_out = 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 | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The gotcha here is that if there is no
Which is also in the above if block, so it could be called just once at the end of this method outside of the if/else block. |
||||
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)) | ||||
|
||||
def add_text(self, text: str, priority: int = 0, key: str = None) -> None: | ||||
"""Add text to display stack. | ||||
|
@@ -171,7 +226,7 @@ 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.") | ||||
self.add_text_entry("", self._previous_color, FlashingType.NO_FLASH, "", None, None, 100, key) | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a good use of an existing method to clear out a display, but the I would suggest instead that this behavior check the current text to see if it has the requested key, and if so remove the text and pass an empty key, and if not do nothing. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would tend to disagree on this point. But maybe I misunderstood something For the On a side note: I am working on one fix for the show If you really believe that the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here's a use case I'm picturing where key and priority would be helpful, let me know what you think. A player starts a non-exclusive hurry-up mode (priority 1000) with a jackpot on the left loop, and that hurry-up mode sends the text "HIT LEFT LOOP" to a display. Instead of going for the hurry-up, the player instead starts a multi ball (priority 5000) and the multi ball mode sends the text "JACKPOT: 5M" to the display. During the multi ball, the hurry-up mode times out and ends. By default, the hurry-up mode is set to remove the "HIT LEFT LOOP" text from the display, so it will send a command to the displays to replace the text with empty string. Without a key, the display doesn't know that the text being shown is not the text that's expected to be removed, and will instead remove the "JACKPOT: 5M" text from the display even though the multi ball is still running. As a result, the multi ball will have a blank display. Reverse the situation and start the multi ball before the hurry-up. The multi ball mode is higher priority so its display text should have precedence, and the hurry-up display text should be ignored. Otherwise the multi ball "JACKPOT: 5M" will turn into "HIT LEFT LOOP" and then disappear after the hurry-up, again leaving the multi ball with a blank display. I realize that this type of use case is exactly what the Thoughts? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The described use case is very valid and is very much a use case for the
indicates that the text is written straight to the display. Can you describe in words what would be the logic of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right, I'm overthinking it and trying to keep stack-like functionality where it doesn't belong. The concept of replace doesn't need to have any considerations for priority or key or what's currently on the display, and should always overwrite when called. |
||||
return | ||||
|
||||
if key in self._text_stack: | ||||
|
@@ -182,9 +237,8 @@ def remove_text_by_key(self, key: Optional[str]): | |||
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, | ||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -76,28 +76,36 @@ def _create_characters(cls, text: str, display_size: int, collapse_dots: bool, c | |
"""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 | ||
- Provided colors are assumed to be for the text itself, thus the list of colors is "right aligned" too | ||
- If list of colors is less than the display size the list will be extended with white as default color on the left endswith | ||
so that the provided colors are for the text and not being used for invisible spaces | ||
""" | ||
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 | ||
color_length = len(colors) | ||
if color_length > display_size: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How much do we really want to handle here? I think it is reasonable for a designer to send one color (for all segments) or a list of colors (one for each segment), and those are the two options. It doesn't seem reasonable to expect MPF to "correctly" handle coloring a seven segment display when you send it four colors or something 🤷. I think we should strip down all this color-filling logic and throw an exception if the list of colors is neither 1 nor the display size. Unless I'm missing a use case? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Very true, that is what I asked myself as well. I tried to be minimal invasive with my code changes, so that is what I came up with. But it seems we have the same understanding here and thus I am happy to change the color handling. Will provide a new PR in the next days including all the other feedback you gave. |
||
for _ in range(color_length - display_size): | ||
colors.pop(0) # remove very left color of array if too long | ||
elif color_length < display_size: | ||
for _ in range(display_size - color_length): | ||
colors.append(RGBColor("white")) # add default color on the left of the array if too few colors | ||
|
||
# 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 _ in range(len(uncolored_chars)): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. By convention, using the underscore as a variable in a loop is meant to indicate that the variable is never used, i.e. the loop code doesn't care which iteration it's on. If you are going to use that variable, a letter is preferred (either the generic There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On second thought, that's not necessary because
|
||
color = colors[_] | ||
char_list.append(DisplayCharacter(uncolored_chars[_][0], uncolored_chars[_][1], uncolored_chars[_][2], color)) | ||
|
||
return char_list | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be more readable (and do the same thing) with a generic truthiness check: