How can I define widget width relative to the window in kv language? - layout

I am trying to create the layout pictured below in a kv file. I have tried several different methods to define a 30% region containing a label, but the label always ends up in the bottom left corner of the screen. I suspect I'm misusing size, size_hint and text_size, and/or that I need a containing element for the label (I've tried FloatLayout).
sample.kv
#:kivy 1.0.9
<SampleGui>:
canvas.before:
Rectangle:
pos: self.pos
size: self.size
source: "background.jpeg"
Label:
text: "Illum qui labore quia sed quia sint quaerat."
size_hint: (0.3, 1)
pos_hint: {'right': 1}
sample.py
from kivy.app import App
from kivy.config import Config
from kivy.uix.widget import Widget
Config.set('graphics', 'window_state', 'maximized')
class SampleGui(Widget):
pass
class SampleApp(App):
def build(self):
return SampleGui()
if __name__ == '__main__':
SampleApp().run()
How can I define allocate a 30% width to the label? (Padding and other details are not important, unless they impact the answer.)

And with the added code, your error becomes obvious. Your SampleGui extends Widget, but the child Label uses attributes (like size_hint and pos_hint) that are only supported by Layout classes. Try changing your SampleGui to extend FloatLayout instead of Widget.

Related

How to use Splitters in kivy with multiple screens?

This is a simple example but essentially I need to use Splitter to separate and resize two individual Text input boxes horizontally, however when I'm trying to use the Splitter widget in a Screen its creating a double behind the original content and the Splitter is not functioning properly. Any help will be appreciated. Thank You.
main file
import kivy
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager , Screen
from kivy.core.window import Window
from kivy.factory import Factory
class TestScreen1(Screen):
pass
class TestScreen2(Screen):
pass
class Manager(ScreenManager):
pass
class MyApp(App):
def build(self):
return Builder.load_file("D:\MainProject\TestFiles\my.kv")
if __name__ == "__main__":
MyApp().run()
KV file
Manager:
TestScreen1:
TestScreen2:
<TestScreen1>:
name:"abc1"
BoxLayout:
orientation: 'horizontal'
Label:
text:"Next"
Splitter:
sizable_from:"left"
Button:
text: "next"
on_press: root.manager.current="abc2"
<TestScreen2>:
name:"abc2"
BoxLayout:
orientation: 'horizontal'
Label:
text:"pre"
Splitter:
sizable_from:"left"
Button:
text: "pre"
on_press: root.manager.current="abc1"
Your kv file is being loaded twice. Once by you Builder.load_file() and once by the App (see documentation). Just remove that Builder.load_file() line.

Heavy movement blur in Kivy app. Possible bug?

Recently I started to work on an old project (which I started on Kivy 1.8.0). I decided to upgrade Kivy to the latest version and migrate the code, when I noticed that every animation on my app was heavily blurred. Without knowing if the problem was some incompatibility between versions, I wrote a small piece of code that should have worked.
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen, ScreenManager
kv = '''
MyScreenManager:
ScreenA:
ScreenB:
<ScreenA>:
name: "A"
canvas:
Color:
rgba: .7,0,0,.5
Rectangle:
pos: self.pos
size: self.size
Button:
size_hint: None, None
center: root.center
on_press: root.parent.current = "B"
Image:
source: "duck.png"
size_hint: None, None
size: 200,200
pos_hint: {"center":(.8,.5)}
<ScreenB>:
name: "B"
canvas:
Color:
rgba: 0,0,.7,.5
Rectangle:
pos: self.pos
size: self.size
Button:
size_hint: None, None
center: root.center
on_press: root.parent.current = "A"
'''
class MyScreenManager(ScreenManager):
pass
class ScreenA(Screen):
pass
class ScreenB(Screen):
pass
class TestApp(App):
def build(self, *args, **kwargs):
return Builder.load_string(kv)
if __name__ == '__main__':
TestApp().run()
This should create two screens, one blue and one red, both with a button that changes the screen, and one of them with an image. I made two short clips to show what I got. The first is the above piece of code in action, the second is just a RecycleView.
https://www.youtube.com/watch?v=4LCAVdRALg4
https://www.youtube.com/watch?v=TLlNBoJcv7M
I'm using Kivy v1.11.1 and Python v3.7.7 (but got the same results on 3.5) on a 64-bit Windows 10.
I couldn't find anything related to this problem so far. Any ideas?
This looks like a graphics driver issue, although I don't know what component of a Kivy version upgrade would have change to trigger it. Make sure your drivers are up to date.

Add custom widgets at runtime to screens

I want to add Buttons (Basicly custom Buttons with Image) as a custom Widgets to "Screen1" but I always end up with "_event.pyx not found" Error.
I've tried with "super().init(**kwargs)" and without.
Python code:
sm = ScreenManager()
class DrinkWidget(Widget):
pass
class HomeScreen(BoxLayout):
def switch(self, to):
#Swithing funktion
#This is the Part, that causes the Problem I think:
class Screen1(Screen):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.add_widget(DrinkWidget(
lable_text_optn = 'test'
))
class Screen2(Screen):
pass
class ZapfanlageApp(App):
icon = 'GUI_Elemente/app_icon.png'
title = 'Zapfanlage'
def build(self):
pass
if __name__ == "__main__":
ZapfanlageApp().run()
Kivy code (separate .kv File. The part "HomeScreen" works so far):
HomeScreen:
sm: sm
name: 'ScreenManager'
BoxLayout:
orientation: 'vertical'
rows: 2
ActionBar:
pos_hint: {'top': 1}
size_hint_y: .065
ActionView:
ActionButton:
text: 'Cocktails'
on_press:
root.switch(1)
ActionButton:
text: 'Drinks'
on_press:
root.switch(2)
ActionButton:
text: 'Einstellungen'
on_press:
root.switch(3)
ScreenManager:
id: sm
size_hint_y: .935
Screen1:
name: "screen1"
id: screen1
Screen2:
name: "screen2"
id: screen2
<Screen1#Screen>:
name: "screen_1"
id: screen1
#Here should the Buttons in GridLayout appear
<Screen2#Screen>:
name: "screen_2"
id: screen2
#This is the Custom Button I want to be inserted above
<Drink_Widget#Button>:
image_path_optn: image_path
lable_text_optn: lable_text
Button:
size_hint_x: None
size_hint_y: None
height: (root.height) -10
width: 250
on_press:
BoxLayout:
orientation: "vertical"
width: root.width
height: root.height
pos_hint: root.pos
pos: root.pos
padding: 5
Image:
source: image_path
Label:
text: label_text
I want to show a various number of DrinkWidgets on screen1 vertically and add them in runtime. But I always end up with nothing showing up or with _event.pyx not found error. Passing the code under <Screen1#Screen>: directly works.
I hope someone can help me. Thanks a lot!
Okay, it looks like you want to add a number of your DrinkWidgets to your screen when your app loads. First things first, in your .py file you have defined a class named Drink_widget but in .kv you call it DrinkWidget
Next, since you have your DrinkWidget defined as inheriting the Button class from kivy, you can easily change the text in the DrinkWidget using the text: field. Similarly, you can change the image that the button displays to be whatever you like using the background_normal: field. To change the image displayed when you click the button, use the background_down: field. Example:
<DrinkWidget#Button>:
text: "some text"
background_normal: "image1.png"
background_down: "image2.png"
So you don't need your lable_text_optn or image_path_optn fields.
Also, you are trying to add a number of widgets to a Screen widget, when really you should be adding a number of widgets to a Layout widget (FloatLayout, BoxLayout, or GridLayout). Your Screen widget should only have the Layout widget as its direct child.
Another issue I see is you have two root widgets inside your .kv file -- HomeScreen and BoxLayout unless your indentation is correct in the question.
Here is a minimal example of what I believe you are trying to get working:
main.py
from kivy.app import App
from kivy.uix.button import Button
class DrinkWidget(Button):
pass
class MainApp(App):
def on_start(self):
# This command is automatically called when your app loads up
the_screen_grid = self.root.ids.some_descriptive_id
# self.root refers to the root widget, which is the GridLayout
# self.root.ids gets me a DictProperty of all children widgets that have an id associated with them
# self.root.ids.some_descriptive_id gets me the GridLayout widget I defined with the id: some_descriptive_id
for i in range(3):
the_screen_grid.add_widget(DrinkWidget(text="drink " + str(i)))
MainApp().run()
main.kv
GridLayout:
cols: 1
Screen:
# Widgets that aren't Layouts normally only have 1 child widget
# To "add multiple widgets" to a screen, give the screen a Layout, then add widgets to the layout
GridLayout:
id: some_descriptive_id
rows: 1
Your code is a bit too long to give you an exact solution for your case, but I hope this example gives you the knowledge to fix it up for yourself!

Widgets keep repeating/duplicating in KV file

I have a UI designed in KV language, as follows:
<MainLayout>:
BoxLayout:
orientation:"horizontal"
BoxLayout:
orientation:"vertical"
Button:
size_hint:(1,.9)
text:"1"
Button:
size_hint:(1,.1)
text:"2"
Here is the accompanying py file:
from kivy import Config
from kivy.app import App, Builder
from kivy.uix.boxlayout import BoxLayout
with open('gui.kv', 'r') as kv:
Builder.load_string(kv.read())
class MainLayout(BoxLayout):
def __init__(self, **kwargs):
super(MainLayout, self).__init__(**kwargs)
class GuiApp(App):
Config.set('graphics', 'resizable', '0')
Config.set('graphics', 'width', '1600')
Config.set('graphics', 'height', '1000')
def build(self):
self.title = "TestGUI"
return MainLayout()
if __name__ == '__main__':
GuiApp().run()
I want an asymmetrical layout, where the main window has two columns, and the left column has two widgets (top and bottom halves) and the right column is similar.
Each of the widgets in each column needs to be asymmetric in height.
For whatever reason, the given design above keeps "repeating" my left and right halves, there is no "blank" space.
I wanted two BoxLayout, one for each column. My <MainLayout> is also a BoxLayout. I have tried making that a GridLayout with cols:2 as well, but I get similar results.
This is my output:
So, tl;dr, the left column is exactly correct, but it repeats to the right half as well. How can I change that?
Problem - Two Root Widgets
You are getting the column repeated because there are two root widgets. One from Builder.load_string(kv.read()) and the other one from class GuiApp() i.e. by Builder and by name convention respectively.
Solution
Remove the following:
with open('gui.kv', 'r') as kv:
Builder.load_string(kv.read())
Since <MainLayout> is a BoxLayout, therefore you do not need another BoxLayout. Please refer to the example for details.
Note
How to load KV
There are two ways to load Kv code into your application:
By name convention:
Kivy looks for a Kv file with the same name as your App class in
lowercase, minus “App” if it ends with ‘App’ e.g:
MyApp -> my.kv
If this file defines a Root Widget it will be attached to the App’s
root attribute and used as the base of the application widget tree.
By Builder You can tell Kivy to directly load a string or a file. If this string or file defines a root widget, it will be returned by
the method:
Builder.load_file('path/to/file.kv')
or:
Builder.load_string(kv_string)
Example
test.kv
#:kivy 1.10.0
<MainLayout>:
orientation:"horizontal"
BoxLayout:
orientation:"vertical"
Button:
size_hint:(1,.9)
text:"1"
Button:
size_hint:(1,.1)
text:"2"
BoxLayout:
orientation:"vertical"
Button:
size_hint:(1,.9)
text:"3"
Button:
size_hint:(1,.1)
text:"4"
main.py
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
class MainLayout(BoxLayout):
pass
class TestApp(App):
def build(self):
return MainLayout()
if __name__ == "__main__":
TestApp().run()
Output

Kivy different type of coordinates

I try to under stand the different types of coordinates:
Global,
Local,
Window and
Widget
coordinates.
using the program:
class TargetUI(BoxLayout):
js_type = NumericProperty(0)
def __init__(self, **arg):
super(TargetUI, self).__init__(**arg)
btn1 = Button(text='Hello world ' + str(self.js_type))
self.add_widget(btn1)
def on_touch_up(self, touch):
# here, you don't check if the touch collides or things like that.
# you just need to check if it's a grabbed touch event
Logger.info("in touch up")
Logger.info("global coordinates: " + str(touch.pos))
if self.collide_point(*touch.pos):
touch.push()
# if the touch collides with our widget, let's grab it
touch.grab(self)
Logger.info("In widget " + str(self.js_type))
touch.apply_transform_2d(self.to_local)
Logger.info("Local coordinates " + str(touch.pos))
touch.apply_transform_2d(self.to_window)
Logger.info("Windows coordinates " + str(touch.pos))
touch.apply_transform_2d(self.to_widget)
Logger.info("Widget coordinates " + str(touch.pos))
touch.ungrab(self)
# and accept the touch.
return True
class CombWidget(Widget):
pass
class MyPaintApp(App):
def build(self):
return CombWidget()
if __name__ == '__main__':
MyPaintApp().run()
and
#:kivy 1.7.1
<CombWidget>:
tg1: tg1
tg2: tg2
tg3: tg3
BoxLayout:
size: root.size
orientation: 'vertical'
padding: 20
TargetUI:
js_type: 1
id: tg1
TargetUI:
js_type: 2
id: tg2
TargetUI:
id: tg3
js_type: 3
All the coordinates written out by on_touch_up is the same, but expected some difference. Why are are all the coordinates the same?
I also expected to see the Button text to end with 1,2 or 3 but their are all 1. How can I make the Button text be depended in self.js_type?
These are useful when there are coordinate changes. For example, with the scatter widget, here is an example where one of the widgets is put in a Scatter and you can move it (somehow it gets back in place when you click it again, but it's convenient). When you do that, you should see that the coordinates are no longer the same. understanding the difference between them is left as an exercise to the reader :)
from kivy.base import runTouchApp
from kivy.lang import Builder
kv = '''
GridLayout:
cols: 2
spacing: 10
ClickBox
Scatter:
ClickBox:
pos: 0, 0
size: self.parent.size
Widget:
ClickBox:
pos: self.parent.pos
size: self.parent.size
<ClickBox#Widget>:
canvas:
Rectangle:
pos: self.pos
size: self.size
on_touch_move:
if self.collide_point(*args[1].pos): print self.to_window(*args[1].pos)
if self.collide_point(*args[1].pos): print self.to_parent(*args[1].pos)
if self.collide_point(*args[1].pos): print self.to_widget(*args[1].pos)
if self.collide_point(*args[1].pos): print self.to_local(*args[1].pos)
'''
if __name__ == '__main__':
runTouchApp(Builder.load_string(kv))
The Documentation for RelativeLayout clarified this issue for me.
Parent coordinates
Other RelativeLayout type widgets are Scatter, ScatterLayout, and
ScrollView. If such a special widget is in the parent stack, only then
does the parent and local coordinate system diverge from the window
coordinate system. For each such widget in the stack, a coordinate
system with (0, 0) of that coordinate system being at the bottom left
corner of that widget is created. Position and touch coordinates
received and read by a widget are in the coordinate system of the most
recent special widget in its parent stack (not including itself) or in
window coordinates if there are none. We
call these coordinates parent coordinates.
So you must use one of the above special widgets for local coordinates to diverge from window coordinates. Tshirtman's answer works because he used the scatter widget which is one of the special widgets.

Resources