Skip to content

Buttons

Omega Core edited this page May 19, 2024 · 7 revisions

The menu is a quite complex feature, so bear with me. Brief explanations are also found in the 'buttons.py' file


The main building blocks of the menu are Buttons. Through them, users can manipulate dynamically any constant they desire (if the respective button is implemented). Let's forget about the types of buttons and what they need to do for a moment and think more abstractly.


Naive implementation

We have buttons, but we need to navigate from one to another. How we do that?

From the start, we wanted to control the movement through D-Pad buttons. They looked like arrows and were quite intuitive to work with for this purpose. Our first, naive, implementation consisted of an imaginary coordinate grid, and each button was assigned a point. Let's say that +X is the right arrow and +Y is the up arrow. We start at (0,0) and incrementing or decrementing the values would be done with the arrows.

This approach needed limiting the increments, because we don't have infinite buttons, right? Ok so let's put a min and max threshold for both X and Y dimensions. This works well, until you have a non-rectangle button pattern on the screen. We didn't want to manually limit each row and column, so, knowing the exact places with no buttons we can get to, we manually checked for each space and redirect the user to the right button.

This works again, but we need to change the exceptions we check everytime a button is added or removed from the menu. Let's not say that it's very counter intuitive for anyone that would like to improve the menu in some way and the code needed to achieve this is just absurdly complex. To add a new button, you would need to:

  1. find the portion of the code where button images are loaded ;
  2. assign a new coordinate value for the button ;
  3. find all the code sections in which you could arrive at that point and previously treated it as an error ;
  4. change all the limits of the buttons (if necessary) ;
  5. make sure you display it's value correctly, doing lot's of copy-pasted code ;

Obviously this is bad practice, just... don't do that. So we scrapped this idea completely.


Abstraction implementation

Welcome to a new era! We started to notice the similarities in the buttons, one of them being the movement.

Let's say we have selected a button. How do we go to another button? We press one of the arrows, simple enough. Wait... so depending on what arrow we press, a different button is selected? This means that each arrow has a unique button it points to from the selected button.

And now we introduce the notion of button linking. Having 4 different options, we can link a button in each of those 4 directions with 4 other buttons, and if nothing is linked to the arrow pressed, you don't move, because you don't have where to move! That's a big brain idea, so that's what we did!

And how does the user know which button is selected? Here color coding comes is. Each button is made up of it's respective quadrant surrounding it and the name, an image with just text, suggestive names for their functionalities. In our team's branding, green and white are the two main colors, so we decided to make the selected buttons -- light green and not selected buttons -- white.

One more concept we want to talk about is link remembrance. In some particular cases, you would like to override the linking aspect of the button, and instead go to the button you came from. This is very useful when buttons travel multiple menus and have multiple other buttons to point to. Instead of selecting from those, it just goes back to the one it came from. This 'remembrance' can be applied for each of the 4 directions of movement.

You can also link remembrances from a button to another. You can see this feature used for the 'home' button. it uses the same remembrances as the 'options' button.

Confused yet? There's more to come...


Button types

Having a base of abstraction, an AbstractButton class, we can further expand it into different types of buttons, depending on their uses. Our classification looks something like this:

  • empty button: used only for moving through buttons. Can't be pressed but can be selected ;
  • dynamic button: used for faster moving through buttons. When clicked, it automatically selects other button ;
  • toggle button: used for ON/OFF menu applications. Supports different links for each state ;
  • input button: it's a modified toggle button. It gets input from the keyboard. It supports: cms, percents, colors, paths and fonts, tho not all are implemented ;
  • bool button: maybe the most simple button, is the base of the toggle button. Just sets constants ON/OFF, doesn't have any impact on the interface appearance ;

Let's take them one by one:

The Empty Button was created for the sole purpose of having an instance to select. We wanted to have gost-buttons, unable to be pressed, unable to be selected and unable to be seen. This is used on the main menu, it's the main button selected when entering the menu.

The Dynamic Button is more complex. It needs to know the image / surface to display when not selected and the one to display when it's selected. It has a separate link, which is activated when the button is pressed. This external link allows the user to move through different menu pages.

The Toggle Button allows the user to toggle different menus. When pressed, this button works as a boolean, having ON and OFF states. This button also can have separate rows of links for each of those two states.

The Input Button is the most unique one. It's the only button that generates a surface from text input, firstly the constant's value, then the user's input. It needs to know what type of value does it store, as described above.
Using pygame's keyboard input reciever, the simulator collects each character as the input. When the button is pressed, it enters writing mode. In this mode, the value displayed will dissapear, and instead show a "_" character. The text list From now, every character will be added to a list, excepting backspace, which removes the last one, and return which is one way to exit writing mode. Then, depending on what data does the button store, you can write values in.
Exiting with return, by clicking again on the button or by moving to other button, the simulator will check if the input value is valid. If so, congrats, you've just changed a value! Otherwise, the value displayed before will continue to display.

The Bool Button works exactly like a Toggle Button, but it doesn't have extra links. It toggles the value of a constant boolean, not the visibility of some object. It also takes more images as parameters (4 to be exact), for each of the ON and OFF states.
This one, along with other types of buttons, has a 'check()' method. In how we have built this, the values of the constants can change, but their display wouldn't. This resulted into visual bugs. It would only happen when the constants were changed by the simulator itself, not by the user. Not wanting to validate the button appearance each loop, this method was the solution.

Each of the input types (stored as enums) are associated with their respective 'extension' (percent with '%', distance with 'cm' etc.).

Pressing a button is done with the ○ / B button on the controller, but the left bumper can be used to set the current selected button to it's default value. Default in this case means the value it was initialized with, the value set when passing a 'Constants' object to the simulator.


next page →

← previous page