Search code examples
pythonlayoutsplitkivykivy-language

Kivy split label layout div sizes


I am currently trying to implement a custom widget that can have two colors in its' text. It almost works but I have two questions, the first one is: why is there a gap between the green and red BoxLayout div (blackbar in the attached image)? The second is how can I make the split labels to have a width of one div instead of two? I tried many things but nothing worked so far. Any suggestions/ideas are very appreciated! Thanks in advance

Visualisation of my problem. Image

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

Builder.load_string('''
<Split>:
    orientation:"vertical"
    NormalLabel
    NormalLabel
    BoxLayout:
        orientation:"vertical"
        size_hint:1,None
        TopHalfLabel
        BottomHalfLabel   
    NormalLabel
    NormalLabel          


<TopHalfLabel>:
    size_hint:1,None    
    markup:True
    text:"[color=#FF0000]How can I do this?[/color]"
    font_size:30
    height: self.texture_size[1]
    canvas.before:
        Color:
            rgba: 1, 0, 0, 0.3  # Border color (red in this example)
        Rectangle:
            pos: self.pos
            size: self.size
        
<BottomHalfLabel>:
    size_hint:1,None
    markup:True
    text:"[color=#FFFF00]How can I do this?[/color]"
    font_size:30          
    height: self.texture_size[1]
    canvas.before:
        Color:
            rgba: 0, 0, 1, 0.3  # Border color (red in this example)
        Rectangle:
            pos: self.pos
            size: self.size
<NormalLabel>:
    size_hint:1,None
    markup:True
    text:"[color=#FFFF00]How can I do this?[/color]"
    font_size:30          
    height: self.texture_size[1]#
    canvas.before:
        Color:
            rgba: 0, 1, 0, 0.3  # Border color (red in this example)
        Rectangle:
            pos: self.pos
            size: self.size
''')
                    

class NormalLabel(Label):
    pass 

class TopHalfLabel(Label):
    def on_texture_size(self, instance, value):
        #self.texture = self.texture.get_region(0, self.texture.height/2, self.texture.width, self.texture.height/2)
        self.texture = self.texture.get_region(0, self.texture.height/2, self.texture.width, self.texture.height)
  

class BottomHalfLabel(Label):
    def on_texture_size(self, instance, value):
        #self.texture = self.texture.get_region(0, 0, self.texture.width, self.texture.height/2)
        self.texture = self.texture.get_region(0, -self.texture.height/2, self.texture.width, self.texture.height)

class Split(BoxLayout):
    pass

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

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


Solution

  • You can redefine the style of a Widget by prefixing its kv rule with a -. See the documentation. So, I modified your kv rules like this:

    <-TopHalfLabel>:
        size_hint:1,None    
        markup:True
        text:"[color=#FF0000]How can I do this?[/color]"
        font_size:30
        height: self.texture_size[1]/2
        canvas.before:
            Color:
                rgba: 1, 0, 0, 0.3  # Border color (red in this example)
            Rectangle:
                pos: self.pos
                size: self.size
        canvas:
            Color:
                rgba: 1, 1, 1, 1
            Rectangle:
                texture: self.new_texture
                size: self.texture_size[0], self.texture_size[1]/2
                pos: int(self.center_x - self.texture_size[0] / 2.), int(self.center_y - self.texture_size[1]/4)
    
    <-BottomHalfLabel>:
        size_hint:1,None
        markup:True
        text:"[color=#FFFF00]How can I do this?[/color]"
        font_size:30          
        height: self.texture_size[1]/2
        canvas.before:
            Color:
                rgba: 0, 0, 1, 0.3  # Border color (red in this example)
            Rectangle:
                pos: self.pos
                size: self.size
        canvas:
            Color:
                rgba: 1, 1, 1, 1
            Rectangle:
                texture: self.new_texture
                size: self.texture_size[0], self.texture_size[1]/2
                pos: int(self.center_x - self.texture_size[0] / 2.), int(self.center_y - self.texture_size[1]/4)
    

    The main changes are setting the height to half the texture height and the addition of the canvas section, which is based on the default kv for a Label, but uses a new_texture property that is defined by modified classes:

    class TopHalfLabel(Label):
        new_texture = ObjectProperty(None)
        def on_texture_size(self, instance, value):
            # self.texture = self.texture.get_region(0, self.texture.height/2, self.texture.width, self.texture.height/2)
            self.new_texture = self.texture.get_region(0, self.texture.height / 2, self.texture.width, self.texture.height/2)
    
    
    class BottomHalfLabel(Label):
        new_texture = ObjectProperty(None)
        def on_texture_size(self, instance, value):
            # self.texture = self.texture.get_region(0, 0, self.texture.width, self.texture.height/2)
            self.new_texture = self.texture.get_region(0, 0, self.texture.width, self.texture.height/2)
    

    I also added a height: self.minimum_height in the containing BoxLayout:

    <Split>:
        orientation:"vertical"
        NormalLabel
        NormalLabel
        BoxLayout:
            orientation:"vertical"
            size_hint:1,None
            height: self.minimum_height
            TopHalfLabel
            BottomHalfLabel   
        NormalLabel
        NormalLabel          
    

    I think this does what you want.