Guide to Simpleline

Simpleline is a text user interface framework written completely in Python 3 with a possibility to have non-python event loops. With the exception of optional event loops, Simpleline has almost no dependency on external libraries.

This UI is simple and easy to use. It is designed to be used with line-based machines and tools (e.g. serial console) so that every new line is appended to the bottom of the screen. Printed lines are never rewritten!

Basic components

For every application, the following parts are always required.

  • The App static class to initialize and run application.
  • The ScreenHandler static class for scheduling screens.
  • The UIScreen based classes to create screens which will form the application.
  • Widgets to show anything on the screens.
  • Containers to position widgets on the screen.

Look at the next section to see how everything fits together.

How to create a simple application

Interaction with a user is necessary to have a useful UI framework. To show anything to a user the UIScreen class must be used. So we will subclass this class to create our screen and set a title for it:

class DividerScreen(UIScreen):

    def __init__(self):
        # Set title of the screen.
        super().__init__(title=u"Divider")
        self._message = 0

The self._message variable will be used later to show results to the user.

The screen’s main purpose is to present content to a user. For this we need widgets and containers.

The UIScreen.window attribute is the most important part of the screen for rendering. It contains the WindowContainer container which is created and filled up by the UIScreen.refresh() method. Everything added to this container is printed to the monitor. We should override the UIScreen.refresh() method and call the parent’s version to prepare the container. Then we can add widgets to the container. The screen will continue like this:

    def refresh(self, args=None):
        # Fill the self.window attribute by the WindowContainer and set screen title as header.
        super().refresh()

        widget = TextWidget("Result: " + str(self._message))
        self.window.add_with_separator(widget)

The WindowContainer.add_with_separator() method will print a blank line after the TextWidget's text.

Now the user has the header and result printed on the screen but it would be nice to give them a hint about how to use the Divider screen. The best part of the screen for this is the Prompt. The Prompt class is responsible for guiding the user by giving them a set of possible options to choose from.

We will set the message of the prompt inside of the UIScreen.prompt() method. We also remove the default option to continue, because it functions the same as quitting when there is only one screen in the application.

    def prompt(self, args=None):
        # Change user prompt
        prompt = super().prompt()

        # Set message to the user prompt. Give a user hint how he/she may control our application.
        prompt.set_message("Pass numbers to divider in a format: 'num / num'")

        # Remove continue option from the control. There is no need for that
        # when we have only one screen.
        prompt.remove_option('c')

        return prompt

When we are able to present our content to a user, we want to have the possibility to process user input. For this purpose there is the UIScreen.input() method. Input from a user is passed to this method, and the screen may process it, discard it or return it for further processing.

If input processing is not required and the screen should only be used for displaying information to a user, then the UIScreen.input_required property should be set to False. However, in this case you need to close, redraw or push a new screen manually. This can be done, for example, in the UIScreen.show_all() method.

    def input(self, args, key):
        """Process input from user and catch numbers with '/' symbol."""

        # Test if user passed valid input for divider.
        # This will basically take number + number and nothing else and only positive numbers.
        groups = re.match(r'(\d+) *\/ *(\d+)$', key)
        if groups:
            num1 = int(groups[1])
            num2 = int(groups[2])

            # Dividing by zero is not valid so we won't accept this input from the user. New
            # input is then required from the user.
            if num2 == 0:
                return InputState.DISCARDED

            self._message = int(num1 / num2)

            # Because this input is processed we need to show this screen (show the result)
            # again by returning PROCESSED_AND_REDRAW.
            # This will call the refresh method so our new result will be processed inside
            # of the refresh() method.
            return InputState.PROCESSED_AND_REDRAW
        else:
            # Not input for our screen, try other default inputs. This will result in the
            # same state as DISCARDED when no default option is used.
            return key

Our screen is finished. Next, we need to use it in our application. To run an application the App static class must be used.

This class will initialize an event loop and the scheduler by the App.initialize() method. When the application is initialized, we need to pass our screen to the screen stack (you can pass multiple screens but we have only one here). To pass a screen to the screen stack we will use ScreenHandler. For further explanation on screen scheduling, refer to the Screen Handling section.

if __name__ == "__main__":
    # Initialize application (create scheduler and event loop).
    App.initialize()

    # Create our screen.
    screen = DividerScreen()

    # Schedule screen to the screen scheduler.
    # This can be called only after App.initialize().
    ScreenHandler.schedule_screen(screen)

    # Run the application. You must have some screen scheduled
    # otherwise it will end in an infinite loop.
    App.run()

Well done! You have your first application in Simpleline.

Further reading

I would recommend everyone who wants to use Simpleline to look at the examples which is one of the best sources of information. Another place to look is the Public API documentation section.