Doton and multiple pages

We can display only 6 tiles on the screen. It is not too much. But when we redesigned GUI we prepared touch spots to scroll between pages.
A page is a group of maximum 6 tiles so one page fits on the screen. And with a few pages, we can have much more information available.
I’m little afraid of inaccuracy of touch panel. Why? See that scroll down area overlaps our two widget buttons. We may be forced to move them up to the middle row. But we will see πŸ™‚

Planning

First, let us look at the placement of the scroll areas:

We have two of them, on top and bottom.
Our window manager got some basic pages functionality in the previous part, we need to improve them and add what is yet required.
If we add a widget on page greater than 0, we need to create this page and add it to the dictionary. There is one big but, we must create pages in ascending order. It is impossible to add something to page 2 without having pages 0 and 1.
Let’s get to work!

Pages

First, let’s add the creation of page when required page is not available and can be created.

    def _add_page(self, page):
        """add new page"""
        if len(self.pages) < page:
            raise Exception('Cannot create page '+str(page))

        self.pages.append(Page())

and move one widget toΒ  page #1 (#0 is default):

window_manager.add_widget('node-kitchen', [(0, 0)], NodeOneWidget(FONTS['24x42']), 1)

Time for something harder, we need to add checker if scrollable are was pressed. And we need to check it before event propagation to a widget that may be under area.

Defined size of areas was way to small. I could only hit it via a pen but not by finger. For now, I extended their range but I think we need a good calibration algorithm πŸ™‚ and better screen.
Window manager got new function:

    def _execute_internal_event(self, point):
        """execute internal event"""
        if 75 < point[0] < 165 and 0 < point[1] < 60:
            print("up")
            return True
        if 75 < point[0] < 165 and 260 < point[1] < 320:
            print("down")
            return True
        return False

It is executed in at the beginning of click function so we first check for internal events:

    def click(self, point):
        """execute click event"""
        if self._execute_internal_event(point):
            return

Now we have a bigger problem. Imagine that we have 6 tiles on page #0 and we scroll to page #1. On page #1 we have only one tile.
With current drawing function, we will paint new widget but old ones are still visible. We need to do something scary – repaint the whole area and then paint tiles. Why it is scary? Because access to LCD is slow and it takes a few seconds to repaint whole display. But I do not see a better way.

Also, we need to change function get_widgets to return all widgets from all pages.

Some changes in run function are also needed:

   def run(self):
        """main loop - drawing"""
        self.widgets = self.pages[self.active_page].widgets
        while self.work:
            if self.draw_page:
                self._draw_widgets()
            for holder in self.widgets:
                self.widgets[holder].widget.draw_values(
                    self.lcd, self.widgets[holder].coords
                )
            time.sleep(0.025)

    def _draw_widgets(self):
        """draw widgets"""
        self.lcd.background_color = (0, 0, 0)
        self.lcd.fill_rect(0, 0, self.lcd.width, self.lcd.height)
        for holder in self.widgets:
            self.widgets[holder].widget.draw_widget(
                self.lcd, self.widgets[holder].coords
            )

        self.draw_page = False

and now we can finish scrolling:

   def _execute_internal_event(self, point):
        """execute internal event"""
        if 75 < point[0] < 165 and 0 < point[1] < 60:
            self._page_previous()
            return True
        if 75 < point[0] < 165 and 260 < point[1] < 320: 
           self._page_next() 
           return True r
        eturn False 

    def _page_previous(self): 
        """switch to prev page""" 
        if self.active_page > 0:
 
            self.widgets = self.pages[self.active_page-1].widgets
            self.draw_page = True
            self.active_page -= 1

    def _page_next(self):
        """switch to next page"""
        if self.active_page < len(self.pages)-1:
  
            self.widgets = self.pages[self.active_page+1].widgets
            self.draw_page = True
            self.active_page += 1

With such construction, all widgets are working and receiving data but visible are only those on the active page.
After scroll, the screen is fully redrawn and refreshed. As it should be. Sadly it takes some time 😦
But maybe there is a faster way? We can try and reset a display instead of drawing a black rectangle, do:

self.lcd.init()

This works instantly and we can see if we pressed the scroll area πŸ™‚

After few fixes with refreshing this part is completed.

One widget to many pages

What if we wanted to have the same widget on page #1 and #0? Now it is impossible because we have a unique name as a key and this name connects the to handler.

Let’s do something with it.

There are two aspects, adding widget the to the window manager and adding widgets to handlers dispatcher. They are tightly connected. We need to cut this connection.
Adding a widget to the manager will stay the same, it must have a unique name.
But instead of passing widgets from manager to the dispatcher we will pass a multi-dimensional dictionary, with node names as the key and a dictionary of attached widgets as values.

With this in mind, we can change handler to:

"""Used to get data from handlers and pass it to correct widget"""


class HandlerDispatcher(object):
    """class HandlerDispatcher"""
    def __init__(self, widgets):
        self.widgets = widgets

    def set_dht_data(self, node, temp, humi):
        """get gdata from DHT11 sensor"""
        if node in self.widgets:
            list(map(lambda item:item.change_values({
                'temp': temp,
                'humi': humi
            }), self.widgets[node]))

    def set_pir_data(self, node, movement):
        """get data from PIR sensor"""
        if node in self.widgets:
            list(map(lambda item: item.change_values({
                'pir': movement
            }), self.widgets[node]))

    def set_light_data(self, node, light):
        """get data from light detector"""
        if node in self.widgets:
            list(map(lambda item: item.change_values({
                'light': light
            }), self.widgets[node]))

    def set_relay_states(self, node, states):
        """get data from relay"""
        if node in self.widgets:
            list(map(lambda item: item.change_values({
                'states': states
            }), self.widgets[node]))

I must say that such usage of map function looks strange, anyone knows a better way of doing it?

With this, we have many possibilities of reusing widgets:

window_manager.add_widget('openweather', [(0, 1), (1, 1)], OpenweatherWidget(FONTS))
window_manager.add_widget(
    'my-room-light', [(0, 2), (1, 2)],
    RelayWidget(msg, 'my-room-light', broadcast_socket, address, 2)
)
window_manager.add_widget('node-my-room-2', [(1, 0)], NodeOneWidget(FONTS['24x42']))

window_manager.add_widget('node-kitchen', [(0, 1)], NodeOneWidget(FONTS['24x42']), 1)
window_manager.add_widget('node-my-room', [(1, 2)], NodeOneWidget(FONTS['24x42']), 1)

window_manager.add_widget('more-weather', [(0, 2), (1, 2)], window_manager.get_widget('openweather'), 2)

window_manager.set_widget_color('node-my-room', 'background', (0, 255, 255))
window_manager.start()

dispatcher = HandlerDispatcher({
    'node-kitchen': [window_manager.get_widget('node-kitchen')],
    'node-my-room': [window_manager.get_widget('node-my-room'), window_manager.get_widget('node-my-room-2')],
    'openweather': [window_manager.get_widget('openweather')],
    'my-room-light': [window_manager.get_widget('my-room-light')]
})

See that we may reuse a widget on a different page or have two or more that use the same handler.

Summary

Another nice feature arrived, we can scroll between pages. With this, we may have more than 6 tiles. One screen may be with the weather, one with lights and another with a music control (hmm this is good idea πŸ˜€ ).

And another addition is the ability to use the same widget on many pages or hook different widgets to the same handler (data source).

 

Advertisements

One comment

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