Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can the Kivy language access inherited layouts and widgets?

Can the kivy language access inherited layouts and widgets? I want to create one basic BoxLayout that contains the styling and title Label for my widget. I want to be able to inherit from this widget and add additional widgets in different positions.

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout

Builder.load_string('''
<SimpleBar>:
    canvas.before:
        Color:
            rgba: 0, 0.5, 0.5, 1
        Rectangle:
            pos: self.pos
            size: self.size
    BoxLayout:
        id: my_layout
        Label:
            text: "hi"

<NewBar>:
    Label:
        text: "2"
''')

class SimpleBar(BoxLayout):
    def log(self, value):
        print(value)

class NewBar(SimpleBar):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        print(dir(self))

class GeneralApp(App):
    def build(self):
        return NewBar()

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

Above is my basic running widget.

I want NewBar's "2" Label to be located before SimpleBar's 'hi' Label like below.

<NewBar>:
     BoxLayout:
         id: my_layout
         Label:
             text: "2"
         Label:
             text: "hi"

I know that - can negate items. However, <-NewBar> removes all of my styling.

Is there any way to do this in the kivy language?

like image 235
justengel Avatar asked Jul 29 '16 02:07

justengel


People also ask

Which language is used to design the custom widget for Kivy application?

The KV language, sometimes called kvlang or the kivy language, allows you to create your widget tree in a declarative way and to bind widget properties to each other or to callbacks in a natural manner.

Is Kivy a programming language?

Kivy is a free and open source Python framework for developing mobile apps and other multitouch application software with a natural user interface (NUI). It is distributed under the terms of the MIT License, and can run on Android, iOS, Linux, macOS, and Windows.

Which among the following class is used for defining layout in Kivy?

PageLayout: Used to create simple multi-page layouts, in a way that allows easy flipping from one page to another using borders.


2 Answers

Here's a fun thing: you don't need to specify all classes used in kv lang in the lang itself - you can also add them using Factory.register method later in code. Here's an example:

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.lang import Builder
from kivy.factory import Factory

from functools import partial

Builder.load_string('''

<MyWidget>:
    Foo
    Bar
''')

class MyWidget(BoxLayout):
    pass

class MyApp(App):
    def build(self):
        Factory.register('Foo', cls=partial(Label, text='foo'))
        Factory.register('Bar', cls=partial(Label, text='bar'))
        return MyWidget()

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

Let's use it to create a template base widget we later fill with various content. We use a placeholder that we later replace with another widget:

<BaseWidget>:
    orientation: 'vertical'
    Label:
        size_hint: None, 0.1
        text: 'title'
    Placeholder

In the Python code we register a placeholder class in the __init__ method of this base template class.

class BaseWidget(BoxLayout):
    def __init__(self, **args):
        # unregister if already registered...
        Factory.unregister('Placeholder')
        Factory.register('Placeholder', cls=self.placeholder)
        super(BaseWidget, self).__init__(**args)

Now let's define a content class.

<TwoButtonWidget>:
    Button:
        text: 'button 1'
    Button:
        text: 'button 2'

And finally create a customized class that use our base class as a template and replaces its placeholder with a content class. This class don't have its own kivy rules (these are moved into content class) so when we inherite from our base template, no extra widgets are inserted.

# content class
class TwoButtonWidget(BoxLayout):
    pass

# Base class subclass
class CustomizedWidget(BaseWidget):
    placeholder = TwoButtonWidget # set contetnt class

A full example:

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
from kivy.factory import Factory

Builder.load_string('''
<BaseWidget>:
    orientation: 'vertical'
    widget_title: widget_title
    placeholder: placeholder
    Label:
        size_hint: None, 0.1
        id: widget_title
    Placeholder
        id: placeholder

<TwoButtonWidget>:
    button1: button1
    Button:
        text: 'button 1'
        id: button1
    Button:
        text: 'button 2'

<ThreeButtonWidget>:
    orientation: 'vertical'
    Button:
        text: 'button a'
    Button:
        text: 'button b'
    Button:
        text: 'button c'
''')

class BaseWidget(BoxLayout):
    def __init__(self, **args):
        # unregister if already registered...
        Factory.unregister('Placeholder')
        Factory.register('Placeholder', cls=self.placeholder)
        super(BaseWidget, self).__init__(**args)

class TwoButtonWidget(BoxLayout):
    pass

class ThreeButtonWidget(BoxLayout):
    pass

class CustomizedWidget1(BaseWidget):
    placeholder = TwoButtonWidget

class CustomizedWidget2(BaseWidget):
    placeholder = ThreeButtonWidget

class MyApp(App):
    def build(self):
        layout = BoxLayout()
        c1 = CustomizedWidget1()
        # we can access base widget...
        c1.widget_title.text = 'First'
        # we can access placeholder
        c1.placeholder.button1.text = 'This was 1 before'

        c2 = CustomizedWidget2()
        c2.widget_title.text = 'Second'

        layout.add_widget(c1)
        layout.add_widget(c2)
        return layout

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

You can easily extend it and for example, have multiple placeholders.

Applying this to your case:

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.lang import Builder
from kivy.factory import Factory

from functools import partial

Builder.load_string('''

<SimpleBar>:
    canvas.before:
        Color:
            rgba: 0, 0.5, 0.5, 1
        Rectangle:
            pos: self.pos
            size: self.size
    BoxLayout:
        Placeholder
        Label:
            text: "hi"

<NewBarContent>:
    Label:
        text: "2"
''')

class SimpleBar(BoxLayout):
    def __init__(self, **args):
        # unregister if already registered...
        Factory.unregister('Placeholder')
        Factory.register('Placeholder', cls=self.placeholder)
        super(SimpleBar, self).__init__(**args)

class NewBarContent(BoxLayout):
    pass

class NewBar(SimpleBar):
    placeholder = NewBarContent

class MyApp(App):
    def build(self):
        return NewBar()

if __name__ == '__main__':
    MyApp().run()
like image 194
Nykakin Avatar answered Sep 30 '22 00:09

Nykakin


With simple kv no, because if you put something in the widget (e.g. Label: ...), it'll call <widget>.add_widget() method and when such a method is called without additional parameters, it will by default place the widget after whatever was already placed before it. Therefore you can either search through the kivy/lang/parser.py file and add such a functionality(PR welcome), or do it within python in hm... __init__ or wherever you'll like to add the widget (maybe after some event).

To do that within python you can call <widget (or self)>.add_widget(<child>, index=<where>) according to the docs. For example:

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ListProperty

Builder.load_string('''
#:import Factory kivy.factory.Factory
<Ninja@Label>:

<SimpleBar>:
    BoxLayout:
        id: my_layout
        Label:
            text: "hi"

<ChildWithBenefits>:
    placebefore:
        [(Factory.Label(text='I am the first!'), 0),
        (Factory.Ninja(text='No, I am!'), 2)]
''')

class SimpleBar(BoxLayout):
    def log(self, value):
        print(value)

class ChildWithBenefits(SimpleBar):
    placebefore = ListProperty([])
    def __init__(self, *args, **kwargs):
        super(ChildWithBenefits, self).__init__(*args, **kwargs)
        for child, index in self.placebefore:
            print child.text
            print type(child)
            self.add_widget(child, index=index)
        self.log('Hello!')

class GeneralApp(App):
    def build(self):
        return ChildWithBenefits()

if __name__ == '__main__':
    GeneralApp().run()
like image 32
Peter Badida Avatar answered Sep 30 '22 02:09

Peter Badida