Matthieu Pizenberg
Matthieu Pizenberg

Reputation: 461

Dropdown partially visible in Kivy (Python)

I am trying to create a generic menu bar in Kivy (GUI for Python) and I am having trouble with dropdown menus. They only partially appear and I don't know why (see under Submenu 1):

enter image description here

Here is my code if you want to check it :

#!/usr/bin/env python3

from kivy.app import App
#from kivy.uix.widget import Widget
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.dropdown import DropDown
from kivy.properties import ListProperty, ObjectProperty
#from kivy.uix.actionbar import ActionBar, ActionView, ActionGroup, ActionButton

class MenuItem(ButtonBehavior, Label):
    '''Background color, in the format (r, g, b, a).'''
    background_color_normal = ListProperty([0.2, 0.2, 0.2, 1])
    background_color_down = ListProperty([0.3, 0.3, 0.3, 1])
    background_color = ListProperty()
    separator_color = ListProperty([0.8, 0.8, 0.8, 1])
    pass

class MenuSubmenu(MenuItem):
    # The list of submenu items in dropdown menu
    submenu = ObjectProperty(None)

    def add_widget(self, submenu, **kwargs):
        if isinstance(submenu, MenuDropDown):
            self.submenu = submenu
        super().add_widget(submenu, **kwargs)

    def on_release(self, **kwargs):
        super().on_release(**kwargs)
        self.submenu.open(self)

class MenuDropDown(DropDown):
    pass

class MenuButton(MenuItem):
    pass

class MenuBar(BoxLayout):

    '''Background color, in the format (r, g, b, a).'''
    background_color = ListProperty([0.2, 0.2, 0.2, 1])
    separator_color = ListProperty([0.8, 0.8, 0.8, 1])

    def __init__(self, **kwargs):
        self.itemsList = []
        super().__init__(**kwargs)

    def add_widget(self, item, index=0):
        if not isinstance(item, MenuItem):
            raise TypeError("MenuBar accepts only MenuItem widgets")
        super().add_widget(item, index)
        if index == 0:
            index = len(self.itemsList)
        self.itemsList.insert(index, item)

class MenuApp(App):

    def button(self, nb):
        print("Button", nb, "triggered")

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

And here the kv file :

#:kivy 1.0.9

<MenuItem>:
    background_color: self.background_color_down if self.state=='down' else self.background_color_normal
    color: (1,1,1,1)
    canvas.before:
        Color:
            rgba: self.background_color
        Rectangle:
            pos: self.pos
            size: self.size
    canvas.after:
        Color:
            rgba: self.separator_color
        Line:
            rectangle: self.x,self.y,self.width,self.height

<MenuBar>:
    size_hint_y: None
    height: 40
    canvas.before:
        Color:
            rgba: self.background_color
        Rectangle:
            pos: self.pos
            size: self.size
    canvas.after:
        Color:
            rgba: self.separator_color
        Line:
            points: self.x, self.y, self.x + self.width, self.y

BoxLayout:
    orientation: "vertical"

    MenuBar:
        MenuSubmenu:
            text: "Submenu 1"
            MenuDropDown:
                Button:
                    text: "Button 11"
                    on_release: app.button(11)
                Button:
                    text: "Button 12"
                    on_release: app.button(12)
                Button:
                    text: "Button 13"
                    on_release: app.button(13)
        MenuButton:
            text: "Button 2"
            on_release: app.button(2)
        MenuButton:
            text: "Button 3"
            on_release: app.button(3)

    Button:
        text: "Nothing"
        background_color: 0.4, 0.4, 0.4, 1
        background_normal: ""

It would be great if you know what happen or if you could redirect me to some place more appropriate to find an answer.

Upvotes: 2

Views: 1802

Answers (1)

Matthieu Pizenberg
Matthieu Pizenberg

Reputation: 461

As warned by @inclement I was not using dropdown properly. You should not add directly a dropdown widget in a kv file.

So instead I looked at how was working the actionbar in kivy (explanation here and source code here) and I updated in consequence my menu bar.

Here is then my python code :

#!/usr/bin/env python3

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.widget import Widget
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.dropdown import DropDown
from kivy.uix.spinner import Spinner
from kivy.properties import ListProperty, ObjectProperty,\
        StringProperty, BooleanProperty, NumericProperty
#from kivy.uix.actionbar import ActionBar, ActionView, ActionGroup, ActionButton

class MenuItem(Widget):
    '''Background color, in the format (r, g, b, a).'''
    background_color_normal = ListProperty([0.2, 0.2, 0.2, 1])
    background_color_down = ListProperty([0.3, 0.3, 0.3, 1])
    background_color = ListProperty([])
    separator_color = ListProperty([0.8, 0.8, 0.8, 1])
    text_color = ListProperty([1,1,1,1])
    inside_group = BooleanProperty(False)
    pass

class MenuSubmenu(MenuItem, Spinner):
    triangle = ListProperty()

    def __init__(self, **kwargs):
        self.list_menu_item = []
        super().__init__(**kwargs)
        self.dropdown_cls = MenuDropDown

    def add_widget(self, item):
        self.list_menu_item.append(item)
        self.show_submenu()

    def show_submenu(self):
        self.clear_widgets()
        for item in self.list_menu_item:
            item.inside_group = True
            self._dropdown.add_widget(item)

    def _build_dropdown(self, *largs):
        if self._dropdown:
            self._dropdown.unbind(on_dismiss=self._toggle_dropdown)
            self._dropdown.dismiss()
            self._dropdown = None
        self._dropdown = self.dropdown_cls()
        self._dropdown.bind(on_dismiss=self._toggle_dropdown)

    def _update_dropdown(self, *largs):
        pass

    def _toggle_dropdown(self, *largs):
        self.is_open = not self.is_open
        ddn = self._dropdown
        ddn.size_hint_x = None
        if not ddn.container:
            return
        children = ddn.container.children
        if children:
            ddn.width = max(self.width, max(c.width for c in children))
        else:
            ddn.width = self.width
        for item in children:
            item.size_hint_y = None
            item.height = max([self.height, 48])

    def clear_widgets(self):
        self._dropdown.clear_widgets()

class MenuDropDown(DropDown):
        pass

class MenuButton(MenuItem,Button):
    icon = StringProperty(None, allownone=True)
    pass

class MenuEmptySpace(MenuItem):
    pass

class MenuBar(BoxLayout):

    '''Background color, in the format (r, g, b, a).'''
    background_color = ListProperty([0.2, 0.2, 0.2, 1])
    separator_color = ListProperty([0.8, 0.8, 0.8, 1])

    def __init__(self, **kwargs):
        self.itemsList = []
        super().__init__(**kwargs)

    def add_widget(self, item, index=0):
        if not isinstance(item, MenuItem):
            raise TypeError("MenuBar accepts only MenuItem widgets")
        super().add_widget(item, index)
        if index == 0:
            index = len(self.itemsList)
        self.itemsList.insert(index, item)

if __name__ == '__main__':

    class MenuApp(App):
        def button(self, nb):
            print("Button", nb, "triggered")

    MenuApp().run()

And here is the corresponding file menu.kv :

#:kivy 1.0.9

<MenuButton>:
    size_hint_x: None if not root.inside_group else 1
    width: self.texture_size[0] + 32
    Image:
        allow_stretch: True
        opacity: 1 if root.icon else 0
        source: root.icon
        pos: root.x + 4, root.y + 4
        size: root.width - 8, root.height - 8

<MenuSubmenu>:
    size_hint_x: None
    width: self.texture_size[0] + 32
    triangle: (self.right-14, self.y+7, self.right-7, self.y+7, self.right-7, self.y+14)
    canvas.after:
        Color:
            rgba: self.separator_color
        Triangle:
            points: self.triangle

<MenuButton,MenuSubmenu>:
    background_normal: ""
    background_down: ""
    background_color: self.background_color_down if self.state=='down' else self.background_color_normal
    color: self.text_color

<MenuEmptySpace>:
    size_hint_x: 1

<MenuItem>:
    background_color: self.background_color_normal
    canvas.before:
        Color:
            rgba: self.background_color
        Rectangle:
            pos: self.pos
            size: self.size
    canvas.after:
        Color:
            rgba: self.separator_color
        Line:
            rectangle: self.x,self.y,self.width,self.height

<MenuBar>:
    size_hint_y: None
    height: 48
    canvas.before:
        Color:
            rgba: self.background_color
        Rectangle:
            pos: self.pos
            size: self.size
    canvas.after:
        Color:
            rgba: self.separator_color
        Line:
            rectangle: self.x, self.y, self.width, self.height

BoxLayout:
    orientation: "vertical"

    MenuBar:
        MenuButton:
            icon: "/home/matthieu/internships/singapore/drawings/joytube_color_small.png"
            width: 100
        MenuButton:
            text: "Button 1"
            on_release: app.button(1)
        MenuSubmenu:
            text: "Submenu 2"
            MenuButton:
                text: "Button 21"
                on_release: app.button(21)
            MenuButton:
                text: "Button 22"
                on_release: app.button(22)
            MenuButton:
                text: "Button 23"
                on_release: app.button(23)
        MenuEmptySpace:
        MenuSubmenu:
            text: "Submenu 3"
            MenuButton:
                text: "Button 31"
                on_release: app.button(31)

    Button:
        text: "Nothing"
        background_color: 0.4, 0.4, 0.4, 1
        background_normal: ""

    MenuBar:
        height: 30

Hope it will help other persons wanting to make menu bars.

Upvotes: 3

Related Questions