kivyでメニューバー作ってみた

環境

os x high Sierra
python 3.7.0
kivy 1.10.1

こんな感じ

f:id:b1u3:20180926225605g:plain

ソースコード

#! kivy 1.0.9
# example.kv

<MenuList>:
    canvas.before:
        Color:
            rgba: 1, 0, 0, .5
        Rectangle:
            pos: self.pos
            size: self.size


<MenuImage>:
    source: 'menu.png'
    keep_ratio: True


<MenuBar>:
    end: True
    canvas.before:
        Color:
            rgba: 1, 0, 0, .5
        Rectangle:
            pos: self.pos
            size: self.size

    MenuImage:
        center: 50, root.center_y
        size: 32, 32


<RootWidget>:
    MenuBar:
        pos: root.pos[0], root.height-90
        size: root.width, 90
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.image import Image
from kivy.properties import BooleanProperty, ObjectProperty
from kivy.logger import Logger
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.scrollview import ScrollView


class TestLabel(Label):
    """ For Testing MenuList """
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def on_touch_down(self, touch):
        if self.collide_point(touch.x, touch.y):
            Logger.info(f'[{self.__class__.__name__}\t] {self.text} was touched.')
            return True
        return super().on_touch_down(touch)


class MenuList(ScrollView):

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.layout = GridLayout(cols=1, spacing=20, size_hint_y=None)
        self.layout.bind(minimum_height=self.layout.setter('height'))
        self.do_scroll_x = False
        self.add_widget(self.layout)
        MenuList.test_add_widget(self)

    def add_widget(self, widget, index=0, canvas=None):
        if isinstance(widget, GridLayout) or not self.children:
            super().add_widget(widget)
        else:
            if hasattr(widget, 'text_size'):
                widget.bind(size=self.__class__._update_each_text_rect)
            self.layout.add_widget(widget, index=index, canvas=canvas)

    @staticmethod
    def _update_each_text_rect(instance, value):
        instance.text_size = value[0]-10, value[1]

    @staticmethod
    def test_add_widget(menu_list):
        for i in range(5):
            lbl = TestLabel(text=f'Menu {i}', size_hint_y=None, height=40)
            menu_list.add_widget(lbl)


class MenuImage(Image):
    toggled = BooleanProperty(False)

    def on_touch_down(self, touch):
        Logger.debug(f'[{self.__class__.__name__}\t] touched')
        if self.collide_point(touch.x, touch.y):
            self.toggled = not self.toggled
            return True
        return super().on_touch_down(touch)

    def on_toggled(self, instance, value):
        Logger.info(f'[{self.__class__.__name__}\t] toggled to {value}.')
        if not hasattr(self.parent, 'toggled'):
            Logger.warning(f'[{self.__class__.__name__}\t] {self.parent.__class__.__name__}\
                            has no boolean attr toggled.')
        self.parent.toggled = value


class MenuBar(Widget):
    toggled = BooleanProperty(False)
    menu_list = ObjectProperty(None)

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.menu_list = MenuList()
        self.bind(size=self._update_size, pos=self._update_size)

    def on_toggled(self, instance, value):
        Logger.info(f'[{self.__class__.__name__}\t] toggled to {value}.')
        if self.menu_list is not None:
            if value:
                self.add_widget(self.menu_list)
                self._update_size(self, self.size)
            else:
                self.remove_widget(self.menu_list)

    def _update_size(self, instance, size):
        self.menu_list.size = (280, self.parent.height-90)
        self.menu_list.pos = self.parent.pos


class RootWidget(Widget):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)


class ExampleApp(App):
    def build(self):
        return RootWidget()


if __name__ == '__main__':
    ExampleApp().run()

途中まで書いた下書きが消えたので、以上です。