Python-Kivy: BoxLayout with stretch capability - python

How can I make my boxlayout expand according to dynamically added content?
Here is my code to illustrate what I am trying to do:
main.py
from kivy.app import App
from kivy.uix.label import Label
class TestApp(App):
def add_label(self):
label = Label(text='StackOverflow', color=(0,0,0,1))
self.root.ids.myBox.add_widget(label)
if __name__ == '__main__':
TestApp().run()
test.kv
BoxLayout:
orientation: 'vertical'
spacing: 400
BoxLayout:
orientation: 'vertical'
size_hint: None, None
size: dp(280), dp(100)
pos_hint: {'center_x': 0.5, 'center_y': 0.5}
#width: self.minimum_width
#height: self.minimum_height
id: myBox
background_color: (1,1,1,1)
canvas.before:
Color:
rgba: self.background_color
Rectangle:
pos: self.pos
size: self.size
Button:
text: 'BUTTON'
size_hint: None, None
size: dp(100), dp(50)
pos_hint: {'center_x': 0.5, 'center_y': 0.1}
on_release: app.add_label()
The button has the effect of dynamically adding a label to the boxlayout. But the more the labels are added, the size of the boxlayout does not change and the texts are superimposed.
Thanks in advance for your help.

One way to do this is do recalculate the size of the BoxLayout yourself. By changing your add_label() method to:
def add_label(self):
label = Label(text='StackOverflow', color=(0,0,0,1))
self.root.ids.myBox.add_widget(label)
label.texture_update()
boxlayout = self.root.ids.myBox
height = 0
for child in boxlayout.children:
height += child.texture_size[1]
height += 2 * child.padding_y
boxlayout.height = height
The BoxLayout changes height on each Label addition. You would think that you could use the child.height in the calculation, but the default value for height is always 100 until the layout is actually calculated. But, as you have seen, when the layout is calculated, the height of the label is reduced to fit in the BoxLayout. The texture of the Label can be updated before layout, and therefore, I have used its height in the calculation. This will decrease the size of the BoxLayout with the first Label added, and will increase it on every following addition. Note that this will only work for widgets that have a texture property like the Label or Button.

Related

How can I modify MDButtons' size without using font_size in KivyMD

I'm using KivyMD and trying to resize my MDButtons but it seems like their size only changes based on their font_size which seems to be the KivyMD default setting.
I have tried this docs (using increment_width:), this question even try to set its size_hint and size but it seems hopeless.
So I wonder if is there any way to set the button size individually (not font_size) just like Button in Kivy?
Here is the example, I still don't know how do I get it wrong:
from kivy.lang import Builder
from kivymd.app import MDApp
KV = """
MDScreen:
MDFlatButton:
canvas.before:
Color:
rgba: 1,0,0,1
Rectangle:
size: self.size
pos: self.pos
text: 'MDButton'
increment_width: "164dp" # replacing this with size_hint:None,None then size: doesn't work at all
pos_hint: {'center_x': .5, 'center_y': .5}
"""
class Example(MDApp):
def build(self):
return Builder.load_string(KV)
Example().run()
You can use padding to increase the size of the MDFlatButton:
MDScreen:
MDFlatButton:
canvas.before:
Color:
rgba: 1,0,0,1
Rectangle:
size: self.size
pos: self.pos
padding: 200, 100 # increase the size of the MDFlatButton
text: 'MDButton'
pos_hint: {'center_x': .5, 'center_y': .5}

Background rectangle fit to label text in Kivy

I am trying to get a background around a label that fits the number of lines in the texted in it. For example, a label with text 'Line1\nLine1\nLine3' would have a larger Y dimension that just 'line1'
What currently happens is all of the labels are the same size and clip-off text that doesn't fit within them, the labels are also inside a recycleview layout because I would like to be able to scroll and update large amount of the labels often.
I have tried a few things but have had no luck, and am struggling to get a variable to be understood where I have added # HERE in the .kv file
from kivy.app import App
from kivy.properties import NumericProperty, Clock, ObjectProperty, StringProperty
from kivy.uix.widget import Widget
from kivy.uix.recycleview import RecycleView
from kivy.uix.button import Button
from kivy.uix.label import Label
class TopPostsTest(RecycleView):
def __init__(self, **kwargs):
super().__init__(**kwargs)
message_height = NumericProperty(20)
items = ["hello\ntest\ntest", "test, gghgjhgjhgjhgjhghjghjgjhgjhgjhgjhgjhgjhgjhgjhg", "cheese"]
self.data = [{'text':str(p)} for p in items]
class LabelColor(Label):
pass
class TruthApp(App):
# def build(self):
# return super().build()
pass
if __name__ == "__main__":
TruthApp().run()
<MainControl#PageLayout>:
border: "25dp"
swipe_threshold: 0.4
TopPostsTest:
Settings:
<LabelColor>:
color: 0,0,0,1
text_size: self.size
halign: 'left'
valign: 'top'
font_name: "Assets/Fonts/Nunito-Bold.ttf"
font_size: "12dp"
multiline: True
canvas.before:
Color:
rgba: (0.8, 0.8, 0.8, 1)
RoundedRectangle:
pos: self.pos
size: self.size
radius: [5, 5, 5, 5]
canvas:
Color:
rgba:0,0.9,0.9,1
Line:
width:0.8
rounded_rectangle:(self.x,self.y,self.width,root.height, 5) # HERE
<TopPostsTest>:
viewclass: 'LabelColor'
scroll_y: 1
RecycleBoxLayout:
id: message_view
default_size: None, dp(40) # NewHERE
default_size_hint: 1, None
size_hint_y: None
padding: ["10dp", "16dp"]
spacing: "8dp"
height: self.minimum_height
orientation: 'vertical'
Thank you for any help :)
Edit:
I have found that I have been changing the wrong value and that the variable that needs changing has been marker with # NewHERE, however I am still unable to get it to work or get a variable from the py file into the kv
In order to get a Label that expands vertically as it's text-content grows you can set its height to its texture height.
Also, to fit the text within available space (width) you can change text_size.
Thus you can modify the kvlang for LabelColor as,
<LabelColor>:
color: 0,0,0,1
text_size: self.width, None
size_hint_y: None
height: self.texture_size[1]

Kivy moving label inside FloatLayout

Hello I want to place a welcome message at the top of the app (Like the photo this)
I use a FloatLayout for my entire screen (since I want to add some other widgets later) but the problem is the label won't position itself at the center but like this
Here is my python code:
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.widget import Widget
class MainPage(FloatLayout):
pass
class SmartMirrorApp(App):
def build(self, **kwargs):
return MainPage()
if __name__ == "__main__":
SmartMirrorApp().run()
And this is my kv file:
#:kivy 1.10.1
<MainPage>:
canvas:
Color:
rgba: 1,0,1,0.5
Rectangle:
size: self.size
pos: self.pos
Label:
canvas:
Color:
rgba: 1,0,0,0.5
Rectangle:
size: self.size
pos: self.pos
text: "Welcome, you look beautiful today!"
font_size: 20
size_hint: None, None
size: self.texture_size
pos_hint: {'x': 0.5, 'y': 0.9}
Now if instead of putting 'x':0.5 inside the pos_hint dictionary I use center_x: root.center_x
the image moves to the desired position ONLY if I resize the window but it starts at the position of the second image.
For your pos_hint, use:
pos_hint: {'center_x': 0.5, 'top': 0.9}
That will center the Label horizontally, and the top of the Label will be at 90% of the MainPage height.

Kivy: how to properly make a label rezise in recycleview

I'm trying to build a log viewer on kivy using recycleview since logs can be pretty large. I'm assigning one label widget per line so I can have more control over the text in the future. Some lines will have more text than others so adapted the Label widget to resize according, but when putting that inside recycleview can't seem to be able to control the height of the widget per line anymore, it stays at the same size. What I expect is the label to wrap on the text and adjust height since don't need the extra space between lines. If there's to little text a lot of free space is shown, if I put to much text in the label it floods and label doesn't grow.
One workaround that I tried with different code was to assign at least a 200 lines per label, that seems to work, but I do need more control over each line of text.
This is the example code:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ObjectProperty
Builder.load_string('''
<Row#BoxLayout>:
canvas:
Color:
rgba: 1, 0.1, 0.1, 0.5 #Red Marker
Rectangle:
size: self.size
pos: self.pos
value: ''
orientation: 'vertical'
Label:
text: root.value
text_size: self.width, None
size_hint_y: None
height: self.texture_size[1]
font_size: 20
<LogDisplayWidget>:
rv: rv
orientation: 'vertical'
RecycleView:
id: rv
scroll_type: ['bars', 'content']
scroll_wheel_distance: dp(114)
bar_width: dp(10)
viewclass: 'Row'
RecycleBoxLayout:
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
spacing: dp(2)
''')
class LogDisplayWidget(BoxLayout):
rv = ObjectProperty()
def __init__(self):
super(LogDisplayWidget, self).__init__()
self.load_text()
def load_text(self):
for i in range(10):
line = str(i) + 'This is a test of a bunch of text'
self.rv.data.append({'value': line})
class TestApp(App):
def build(self):
return LogDisplayWidget()
if __name__ == "__main__":
TestApp().run()
enter image description here
enter image description here
Did a code rewrite, the labels appear resized correctly in first page, but getting jerky unexpected results after scrolling, it shows correct label size sometimes then some are to big, and the scroll skips like trying to adjust itself and it fixes size again. Does anyone have a better way to implement this or I'm missing something? I'm suspecting it has something to do with the way the view refreshes
This is the new code:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.recycleview import RecycleView
import random
Builder.load_string('''
<Row#Label>:
canvas.before:
Color:
rgba: 0.8, 0.1, 0.1, 0.5 #Red Marker
Rectangle:
size: self.size
pos: self.pos
text_size: self.width, None
size_hint_y: None
height: self.texture_size[1]
font_size: dp(20)
<RV>:
viewclass: 'Row'
RecycleBoxLayout:
default_size: None, dp(20)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
spacing: dp(3)
''')
class RV(RecycleView):
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
line = ''
for i in range(50):
n = random.randint(0, 1)
if n:
j = random.randint(5, 30)
line = 'Line: ' + str(i+1) + ' This is a test of a bunch of text' * j
else:
line = 'Line: ' + str(i+1) + ' This is a test of a bunch of text'
self.data.append({'text': line})
class TestApp(App):
def build(self):
return RV()
if __name__ == '__main__':
TestApp().run()

Kivy - inheritance from parent?

I have a question concerning inheritance of sizes from parents within Kivy.
My program layout so far is something along the lines of:
GridLayout (3 cols)
|-------> widget
|-------> widget
|-------> boxlayout (with screenmanager)
|-----> relative layouts used within each screen
I'm having an issue whereby the screenmanager windows all default to size 100, 100 regardless of what I change, and I think it's something to do with the different layouts inheriting from each other, but I am unable to find the source.
Can anyone see what I'm doing wrong here, and suggest a fix?
main.py
import kivy
from kivy.app import App
from kivy.core.window import Window
from layout import MainLayout
Window.size = (581, 142)
class MyApp(App):
def build(self):
self.title = 'Test'
return MainLayout()
if __name__ == '__main__':
MyApp().run()
layout.py
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from screentest import ScreenTestWidget
class MainLayout(GridLayout):
def __init__(self, **kwargs):
super(MainLayout, self).__init__(**kwargs)
layout = GridLayout(cols=3, col_default_width=200)
layout.add_widget(Label(text='test'))
layout.add_widget(Label(text='test'))
layout.add_widget(ScreenTestWidget())
self.add_widget(layout)
screentest.py
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
Builder.load_string("""
<MenuScreen>:
RelativeLayout:
Button:
size_hint: None, None
size: root.width, root.height * 0.5
pos: 0, 50
text: 'Settings'
on_press:
root.manager.current = 'settings'
root.manager.transition.direction = 'down'
Button:
size_hint: None, None
size: root.width, root.height * 0.5
pos: 0, 0
text: 'Quit'
on_press: app.stop()
<SettingsScreen>:
RelativeLayout:
Button:
size_hint: None, None
size: root.width, root.height * 0.5
pos: 0, 50
text: 'Settings'
Button:
size_hint: None, None
size: root.width, root.height * 0.5
pos: 0, 0
text: 'Menu'
on_press:
root.manager.current = 'menu'
root.manager.transition.direction = 'up'
""")
class ScreenTestWidget(BoxLayout):
def __init__(self, **kwargs):
super(ScreenTestWidget, self).__init__(**kwargs)
# Create the screen manager
sm = ScreenManager()
sm.add_widget(MenuScreen(name='menu'))
sm.add_widget(SettingsScreen(name='settings'))
self.add_widget(sm)
# Declare both screens
class MenuScreen(Screen):
pass
class SettingsScreen(Screen):
pass
First you create a MainLayout which inherits from GridLayout. This fills the complete screen. But because you never set columns or rows it never works and will not affect children at all.
Then (in MainLayout.__init__) you create a new gridlayout which you add to the MainLayout. MainLayout doesn't work, so size_hint does nothing so the GridLayout will default to size = 100,100. The position is pos = 0,0 again by default. This layout has 3 columns and a col_default_width = 200.
To this GridLayout you now add 2 Labels and the ScreenTestWidget. TheGridlayout has now a width of 600. Everything is display as it should
The ScreenTestWidget has size_hint = 1,1 by default. Thus its size is size = 200, 100. Each screen inside also has size_hint = 1,1 by default, because you never change it. Then each screen has a relativelayout which also has size_hint = 1,1 by default.
Then the buttons in each screen have custom position and resize with the parent. The parent is the Relativelayout. The Relativelayout resizes with the Screen. The Screen resizes with the ScreenTestWidget. The ScreenTestWidget gets resized by the ColumnLayout. The Columnlayout does not resize, because its parent has no cols or rows set and thus doesn't work. Thus it defaults to 100, 100 (but then each child is resized to 200 width).
To fix this you can change the code as following:
class MainLayout(GridLayout):
def __init__(self, **kwargs):
super(MainLayout, self).__init__(**kwargs)
# instead of creating a new Layout set the attributes of this one
self.cols = 3
self.col_default_width = 200
self.add_widget(Label(text='test'))
self.add_widget(Label(text='test'))
self.add_widget(ScreenTestWidget())
Also you don't need the RelativeLayouts in the screens, because Screens are already RelativeLayouts:
<SettingsScreen>:
Button:
size_hint: None, None
size: root.width, root.height * 0.5
pos: 0, 50
text: 'Settings'
Button:
size_hint: None, None
size: root.width, root.height * 0.5
pos: 0, 0
text: 'Menu'
on_press:
root.manager.current = 'menu'
root.manager.transition.direction = 'up'
Alternatively you could also set the either rows or cols for the MainLayout.

Categories