Piader v2 – views

In this article we will work on views and create our first view for home tab. We could put all code into one file but it would be a mess. So lets take a different approach, we will create a separate file for each view. Next we will work on button widget and create quit game action. Don’t forget that Piader is a tool to improve LCDManager.

Views

One problem with keeping each view in separate file is lack of communication between them. We could create and use the view manager but it is overkill. Lets keep it simple. Control over views will be in game class. Why do we need control? We need to switch between views, send some basic data (like options to option view) and coordinate all this.

Our views have two states. One when it is active and visible and second when it is inactive and invisible. When it is active loop function will be called.

Each view require initialization – create all widgets, show event – used when view is entering visible state, hide event – used when hiding view and loop – called in each frame, here we store event dispatcher. Show function should also restore view state and set all required information.
We know that we need some functions, lets create an interface.

Create new package abstract. And new file view.py. Content of this file:

#!/usr/bin/python
# -*- coding: utf-8 -*-
#pylint: disable=I0011,R0913,R0902,R0921

"""View interface"""


class View(object):
    """Class for views"""
    def hide(self):
        """on hide event"""
        raise NotImplementedError("hide not implemented")

    def show(self):
        """on show event - used when switching to this view. Should reinitialize view"""
        raise NotImplementedError("show not implemented")

    def loop(self, action):
        """main loop, called from view manager"""
        raise NotImplementedError("loop not implemented")

 

Home view

Create new package views. In this create home.py. In __init__ we will create all widgets. Home screen consist of one tab with 1 label and 3 buttons.ย  Content of home.py:

# -*- coding: utf-8 -*-

""" home view
"""

import abstract.view as view
import lcdmanager.widget.pane as pane
import lcdmanager.widget.button as button
import lcdmanager.widget.label as label


class Home(view.View):
    """Home view"""
    def __init__(self, lcdmanager):
        """create all widgets"""
        self.manager = lcdmanager
        self.pane = pane.Pane(0, 0, 'home')
        self.pane.width = lcdmanager.width
        self.pane.height = lcdmanager.height
        self.active_button = 0
        title = label.Label(self.pane.width / 2 - 5, 0, 'title')
        title.label = "Piader v2.0"
        self.pane.add_widget(title)

        button_start = button.Button(self.pane.width / 2 - 4, 1, 'btn_start')
        button_start.label = " Start "
        self.pane.add_widget(button_start)

        button_options = button.Button(self.pane.width / 2 - 5, 2, 'btn_options')
        button_options.label = " Options "
        self.pane.add_widget(button_options)

        button_quit = button.Button(self.pane.width / 2 - 3, 3, 'btn_quit')
        button_quit.label = " Quit "
        self.pane.add_widget(button_quit)

        self.buttons = [
            button_start, button_options, button_quit
        ]
        self.manager.add_widget(self.pane)

    def hide(self):
        """hide home tab"""
        self.pane.visibility = False

    def show(self):
        """show home tab"""
        self.pane.visibility = True
        self.pane.get_widget('btn_start').event_focus()

    def loop(self, action):
        """tick"""
        pass

Lets quickly explain the code. In init we create label with game title and three buttons. We try to center them on display.
In function show we set start state, set tab as visible and first button as active. Each button has it unique name and additionally we keep all buttons in dictionary for quick access.

Go back to game.py into __init__ function and add just before the last line: (don’t forget about import)

self.views['home'] = home_view.Home(self.game_manager)

Next go to main_loop function and add:

        """main loop"""
        self.event_server.start()
        self.views['home'].show()

Another step is to add:

self.tick()

between ifs and end calculation.
Finally new tick function:

    def tick(self):
        """render view"""
        self.game_manager.render()
        self.game_manager.flush()

We added home view into game class and added display refreshing. When you run main.py you will see something like this:

40x4

40×4

20x4

20×4

 

 

 

 

 

 

Not bad ๐Ÿ™‚

Actions

Lets add event support – moving up and down and switching between buttons. Because we have all buttons in dictionary, switching between them is easy. Changing state goes like this: set current as inactive, increase/decrease pointer and set active button.

New loop function and two helpers:

    def loop(self, action):
        """tick"""
        if action == 'move.up':
            self.buttons[self.active_button].event_blur()
            self._prev_button()
            self.buttons[self.active_button].event_focus()

        if action == 'move.down':
            self.buttons[self.active_button].event_blur()
            self._next_button()
            self.buttons[self.active_button].event_focus()

    def _prev_button(self):
        """select previous button"""
        self.active_button -= 1
        if self.active_button < 0: self.active_button = len(self.buttons) - 1 def _next_button(self): """select next button""" self.active_button += 1 if self.active_button > len(self.buttons) - 1:
            self.active_button = 0

We can control active button! (with W and S). Good ๐Ÿ™‚

But when we press action (space) nothing happens… lets do something about it.

Button callback

We need to go back to LCD Manager because we need to add an action callback. But before this we need to create an action interface.
Create new file action.py in abstract/event package:

#!/usr/bin/python
# -*- coding: utf-8 -*-

"""Action event - abstract class"""


class Action(object):
    """event action"""
    def __init__(self):
        self._callback = None

    def event_action(self, widget):
        """called on action"""
        raise NotImplementedError("event_action not implemented")

    @property
    def callback(self):
        """return callback"""
        return self._callback

    @callback.setter
    def callback(self, func):
        """set callback"""
        self._callback = func

Now we need to make sure Button class implement this, write test:

    def test_should_implement_event_action_interface(self):
        assert_equal(isinstance(self.button, action.Action), True)

We are in nice red state. But when we add another inheritance to Button class and another parent initialization we are back to green. Currently Button inherits three parents:

class Button(widget.Widget, focus.Focus, action.Action):

Lets check if we can attach and call some callback. Write test:

    def test_should_attach_and_call_callback(self):
        self.button.label = " Start game"

        def _log(widget):
            return widget.label

        self.button.callback = _log
        assert_equal(self.button.callback, _log)
        assert_equal(self.button.event_action(), self.button.label)

And resolve:

    def event_action(self):
        """call action attached to widget"""
        return self.callback(self)

See that we are passing calling widget to callback. We may check its label or state in it.

Menu events

Go back to home.py. Add another if to loop function:

        if action == 'action':
            self.buttons[self.active_button].event_action()

Create callbacks:

    def _button_quit(self, widget):
        """quit option"""
        print 'quit'

    def _button_options(self, widget):
        """options"""
        print "options"

    def _button_start(self, widget):
        """start game"""
        print "start game"

And attach them to proper buttons in __init__:

button_start.callback = self._button_start
button_options.callback = self._button_options
button_quit.callback = self._button_quit

That’s it! When you run a code you may move up/down on menu and call actions.
We have three dummy actions. Quit function is the simplest so we will implement it as first one.
Quit routine should be in Game class, so we need to pass it to home view:

    self.views['home'] = home_view.Home(self.game_manager, self)

Modify home view constructor and store it in self.game attribute.
Next change _button_quit to:

    def _button_quit(self, widget):
        """quit option"""
        self.game.quit_game()

And finally add new function to game.py:

    def quit_game(self):
        """quit game"""
        self.option['game_on'] = False

Yey! We can now quit the game ๐Ÿ™‚

Stop! Jenkins time!

This time we have two projects to check. First LCDManager. Problems only with too long lines. Easy to fix.
Next Piader, this time we had many violations. Mostly too long lines and import of libraries that don’t exist on server. Quick fixes and what is left is not used widget parameter in action function.

Summary

We added home view and home actions to Piader. We also added callback support to button widget. And the most important we can quit the game ๐Ÿ™‚

Download LCDManager

Download Piader

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s