Kivy Screen class bug - python

For some reason in kivy when you create a screen class and add a widget to it, specifically an image you get an image 10x bigger than the original for some reason compared to when you make a widget class and add that widget class as a child to the screen class. Here is my code for the kv file:
<StartScreen>
# Start Screen
name:'Start'
orientation: 'vertical'
FloatLayout:
id: Start_Layout
Image:
id: Start_Background
source: r'Images\Battle.jpg'
keep_ratio: True
allow_stretch: True
size: root.size
<MainScreen>
name: 'Main'
orientation: 'vertical'
FloatLayout:
Image:
source: r'Images\Button.png'
allow_stretch: True
keep_ratio: False
size: 100, 100
and for the python gui file...
from kivy.uix.widget import Widget
from kivy.uix.image import Image
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.animation import Animation
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.clock import Clock
from kivy.graphics.context_instructions import Color
from kivy.graphics.vertex_instructions import *
from kivy.core.window import Window
from kivy.app import App
from kivy.lang import Builder
import kivy
kivy.require('1.9.1')
VERSION = '1.9.1'
class GenericButton(Widget):
Builder.load_file('Button.kv')
def __init__(self, **kwargs):
super(GenericButton, self).__init__(**kwargs)
self.Button = self.ids['Button']
self.size = Window.size
def on_touch_down(self, touch):
self.Button.source = r'Images\ButtonPressed.png'
def on_touch_up(self, touch):
self.Button.source = r'Images\Button.png'
class wid(Widget):
def __init__(self, **kwargs):
super(wid, self).__init__(**kwargs)
self.Button = Image(source='Images\Animatio\glow.gif', allow_stretch=False, keep_ratio=True) (pretend its indented cus im new and it wouldn't let me add it to the code block)
self.add_widget(self.Button)
class StartScreen(Screen):
def __init__(self, **kwargs):
super(StartScreen, self).__init__(**kwargs)
#self.Layout = self.ids['Start_Layout']
#self.size = Window.size
#self.Layout.add_widget(GenericButton())
#self.ids['Start_Button'] = self.Layout.children[0]
print self.ids
#print self.ids.Start_Button.size
print self.size[0]/2, self.size[1]/2
class MainScreen(Screen):
def __init__(self, **kwargs):
super(MainScreen, self).__init__(**kwargs)
self.size = Window.size
def on_touch_down(self, touch):
self.Button.source = r'Images\ButtonPressed.png'
def on_touch_up(self, touch):
self.Button.source = r'Images\Button.png'
class ScreenManager(ScreenManager):
def __init__(self, **kwargs):
super(MCGMScreenManager, self).__init__(**kwargs)
Builder.load_file('Gui.kv')
self.add_widget(StartScreen())
self.add_widget(MainScreen())
And the app runs in the main file which i dont see a need to post. But an important thing might be that the app root class is ScreenManager
edit: i messed around a bit and i did this in python but i cleared the children of GenericButton and added the button that GenericButton used to own as a child of StartScreen and same result, a huge unresponsive image.

<MainScreen>
name: 'Main'
orientation: 'vertical'
FloatLayout:
Image:
source: r'Images\Button.png'
allow_stretch: True
keep_ratio: False
size: 100, 100
I didn't check if it's causing your particular issue, but the Image here doesn't take size 100, 100 because its parent (FloatLayout) is a layout class that automatically sets the size and position of its children. In this case, Image will automatically be resized to fill the FloatLayout.
To prevent this, add size_hint: None, None to the Image, to disable automatic resizing in both the horizontal and vertical directions. This generally applies whenever you add something to a Layout.

Related

kivy load camera (zbarscan) on click button

I've just started my first kivy app. The app is intended to start with button "Start Scan" and then show up the QR scanner built with ZBarCam.
I'm using Screens with the ScreenManager to change from the button view to the camera view (with zbarcam), the problem is that I realized that the camera is initialized from the beginning , so before clicking on the button the camera is already on (I know it because the led from the camera is on).
I don't know if Screen should not be used in this case, or if is there a way to tell the app to do not initialize all screens.
The code I'm using is the following:
QrApp.py:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
class QrReader(Screen):
pass
class ScanButton(Screen):
pass
class QrApp(App):
pass
if __name__ == '__main__':
QrApp().run()
qrapp.kv:
ScreenManager:
id: screen_manager
ScanButton:
id: scan_btn
name: 'scan_btn'
manager: 'screen_manager'
QrReader:
id: qr_reader
name: 'qr_reader'
manager: 'screen_manager'
<ScanButton>:
BoxLayout:
orientation: 'vertical'
Button:
text:'Start Scan'
font_size:"50sp"
color: [0, 255, 255, .67]
on_press: app.root.current = 'qr_reader'
<QrReader>:
#:import ZBarCam kivy_garden.zbarcam.ZBarCam
BoxLayout:
orientation: 'vertical'
ZBarCam:
id:qrcodecam
Label:
size_hint: None, None
size: self.texture_size[0], 50
text: ' '.join([str(symbol.data) for symbol in qrcodecam.symbols])
Thanks!
==== ALTERNATIVE BASED IN A COMMENT (still fails) ====
Based on the comment from n4321d I tried to add the ZBarCam as widget in the QrReader Screen. While I can now initiate the camera when the widget is added, I don't see how I can get the symbols that is, the text read from the QR.
This alternative code is the following:
QrApp.py:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.widget import Widget
from kivy.core.window import Window
from kivy.uix.label import Label
from kivy.uix.screenmanager import ScreenManager, Screen
class QrReader(Screen):
def on_enter(self):
from kivy_garden.zbarcam import ZBarCam
zbarcam = ZBarCam()
self.add_widget(zbarcam)
self.add_widget(Label(
text='PRINT SYMBOLS', #' '.join([str(symbol.data) for symbol in zbarcam.symbols] does not work
size_hint=(None,None),
size=(Window.width*0.1, Window.height*0.1),
center=(Window.width*0.3, Window.height*0.5)))
class ScanButton(Screen):
pass
class QrApp(App):
pass
if __name__ == '__main__':
QrApp().run()
qrapp.kv
ScreenManager:
id: screen_manager
ScanButton:
id: scan_btn
name: 'scan_btn'
manager: 'screen_manager'
QrReader:
id: qr_reader
name: 'qr_reader'
manager: 'screen_manager'
<ScanButton>:
BoxLayout:
orientation: 'vertical'
Button:
text:'Start Scan'
font_size:"50sp"
color: [0, 255, 255, .67]
on_press:
app.root.current = 'qr_reader'
<QrReader>:
BoxLayout:
orientation: 'vertical'
====== SOLUTION ========
My workaround solution is posted as an answer to this question here
First:
I would also add an on_leave function where you remove your cam widget otherwise you will keep adding new widgets every time you load it.
I dont have a working cam now , so i cannot test your code. Looking at your code I think you have to bind the text in your Label to the text in zbarcam.symbols with a function: self.label = Label(....); zbarcam.bind(symbols=lambda *x: setattr(self.label, "text", str(x[1]))) or somethign like that.
Here is an example using a random text generator instead of ZBarCam (since i cannot run that).
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.widget import Widget
from kivy.core.window import Window
from kivy.uix.label import Label
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import ListProperty
import random
from kivy.clock import Clock
kv_str = """
ScreenManager:
id: screen_manager
ScanButton:
id: scan_btn
name: 'scan_btn'
manager: 'screen_manager'
QrReader:
id: qr_reader
name: 'qr_reader'
manager: 'screen_manager'
<ScanButton>:
BoxLayout:
orientation: 'vertical'
Button:
text:'Start Scan'
font_size:"50sp"
color: [0, 255, 255, .67]
on_press:
app.root.current = 'qr_reader'
<QrReader>:
BoxLayout:
orientation: 'vertical'
"""
class ZBarCam(Label):
symbols = ListProperty([])
def __init__(self, **kwargs):
super().__init__(**kwargs)
Clock.schedule_interval(self.gen_rand_text, 1)
def gen_rand_text(self, *args):
self.text = random.choice(['aaaaa', 'bbbbb', 'ccccc'])
self.symbols.append(self.text)
if len(self.symbols) > 3:
del self.symbols[0]
class QrReader(Screen):
def on_enter(self):
self.zbarcam = ZBarCam()
self.add_widget(self.zbarcam)
self.label = Label(
text='PRINT SYMBOLS', #' '.join([str(symbol.data) for symbol in zbarcam.symbols] does not work
size_hint=(None,None),
size=(Window.width*0.1, Window.height*0.1),
center=(Window.width*0.3, Window.height*0.5))
self.add_widget(self.label)
self.zbarcam.bind(symbols = lambda *x: setattr(self.label, "text", str(x[1])))
def on_leave(self, *args):
self.remove_widget(self.zbarcam)
self.remove_widget(self.label)
class ScanButton(Screen):
pass
class QrApp(App):
def build(self):
return Builder.load_string(kv_str)
if __name__ == '__main__':
QrApp().run()
if it still does not work you might have to call self.zbarcam.start() too in the on_enter method after adding it
hope this helps
Thanks to answer from n4321d I finally found a solution.
I also struggled releasing the camera, as zbarcam.stop() stops zbarcam but camera was apparently still open (led was on). After digging a lot, I found this workaround which seems to work well to release the camera.
I also merged the button and qr reader in the same Class and I no longer use kv file, in this example. The final code is the following:
QrApp.py:
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.clock import Clock
from kivy_garden.zbarcam import ZBarCam
class QrScanner(BoxLayout):
def __init__(self, **kwargs):
super(QrScanner, self).__init__(**kwargs)
btn1 = Button(text='Scan Me', font_size="50sp")
btn1.bind(on_press=self.callback)
self.add_widget(btn1)
def callback(self, instance):
"""On click button, initiate zbarcam and schedule text reader"""
self.remove_widget(instance) # remove button
self.zbarcam = ZBarCam()
self.add_widget(self.zbarcam)
Clock.schedule_interval(self.read_qr_text, 1)
def read_qr_text(self, *args):
"""Check if zbarcam.symbols is filled and stop scanning in such case"""
if(len(self.zbarcam.symbols) > 0): # when something is detected
self.qr_text = self.zbarcam.symbols[0].data # text from QR
Clock.unschedule(self.read_qr_text, 1)
self.zbarcam.stop() # stop zbarcam
self.zbarcam.ids['xcamera']._camera._device.release() # release camera
class QrApp(App):
def build(self):
return QrScanner()
if __name__ == '__main__':
QrApp().run()
There is no way to lazy-load kivy Screens. Try this as a workaround: Do not let ScreenManager know about all Screens. Use switch_to to remove the current screen and load the desired one instead. Check Kivy docs to learn how to use it.
Maybe so?
from kivy_garden.zbarcam import ZBarCam
from kivymd.uix.textfield import MDTextField
class MenuScreen(MDScreen):
zbarcam: ZBarCam = ObjectProperty()
qrfield: MDTextField = ObjectProperty()
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.__cam_release()
def scan(self, instance):
self.__cam_release()
def on_touch_down(self, touch):
if self.zbarcam.collide_point(*touch.pos):
self.__cam_open()
return super().on_touch_down(touch)
def __cam_release(self):
if self.zbarcam.xcamera._camera._device.isOpened():
self.zbarcam.stop()
self.zbarcam.xcamera._camera._device.release()
def __cam_open(self):
if not self.zbarcam.xcamera._camera._device.isOpened():
self.zbarcam.xcamera._camera._device.open(0)
self.zbarcam.start()

How to use Kivy Rotate in pure Python code (not kvlang)?

I want to rotate a button with the pure python code instead of the kvlang.
With kvlang we can rotate a button as shown in the example.
The button is rotated 45 degree around its own center. Below is the original code:
from kivy.app import App
from kivy.lang import Builder
kv = '''
FloatLayout:
Button:
text: 'hello world'
size_hint: None, None
pos_hint: {'center_x': .5, 'center_y': .5}
canvas.before:
PushMatrix
Rotate:
angle: 45
origin: self.center
canvas.after:
PopMatrix
'''
class RotationApp(App):
def build(self):
return Builder.load_string(kv)
RotationApp().run()
But when I try to rewrite this example with the pure python code as below, the button is rotated around somewhere else, as shown here:
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.button import Button
from kivy.graphics import PushMatrix, PopMatrix, Rotate
class MyButton(Button):
def __init__(self):
super().__init__()
self.text = 'hello world'
self.size_hint = (None, None)
self.pos_hint = {'center_x': .5, 'center_y': .5}
with self.canvas.before:
PushMatrix()
Rotate(origin=self.center, angle=45)
with self.canvas.after:
PopMatrix()
class RotationApp(App):
def __init__(self):
super().__init__()
self.layout = FloatLayout()
self.button = MyButton()
self.layout.add_widget(self.button)
def build(self):
return self.layout
RotationApp().run()
The above two pieces of code are not producing the same result. Is there anything we did wrong?
UPDATE:
The puzzle is solved as suggested by #inclement, solution as below:
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.button import Button
from kivy.graphics import PushMatrix, PopMatrix, Rotate
class MyButton(Button):
def __init__(self):
super().__init__()
self.text = 'hello world'
self.size_hint = (None, None)
self.pos_hint = {'center_x': .5, 'center_y': .5}
with self.canvas.before:
PushMatrix()
# Rotate(origin=self.center, angle=45) # previous approach
self.rotation = Rotate(origin=self.center, angle=45)
self.bind(center=lambda _, value: setattr(self.rotation, "origin", value))
with self.canvas.after:
PopMatrix()
class RotationApp(App):
def __init__(self):
super().__init__()
self.layout = FloatLayout()
self.button = MyButton()
self.layout.add_widget(self.button)
def build(self):
return self.layout
RotationApp().run()
In your Python code, self.center is evaluated just once during the __init__. In the kv code, a binding is automatically created to reset the Rotate instruction's origin property every time it changes.
You need to put that missing functionality in the Python code, something like self.rotation = Rotate(...) and self.bind(center=lambda instance, value: setattr(self.rotation, "origin", value)) (although I'm sure you can think of a nicer way to set that up, that's just the inline example).

How to remove a dynamically added BoxLayout in Kivy

I've asked in past questions how to add and remove buttons dynamically.
I want to know how to dynamically delete BoxLayout with Python's Kivy.
Here is my code.
#-*- coding: utf-8 -*-
from kivy.config import Config
from kivy.uix.button import Button
Config.set('graphics', 'width', 300)
Config.set('graphics', 'height', 300)
Config.set('input', 'mouse', 'mouse,multitouch_on_demand') # eliminate annoying circle drawing on right click
from kivy.lang import Builder
Builder.load_string("""
<AddItemWidget>:
BoxLayout:
size: root.size
orientation: 'vertical'
RecycleView:
size_hint: 1.0,1.0
BoxLayout:
id: box
orientation: 'vertical'
Button:
id: addButton
text: "Add Item"
on_press: root.buttonClicked()
""")
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.textinput import TextInput
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import StringProperty
class RemovableButton(Button):
def on_touch_down(self, touch):
if touch.button == 'right':
if self.collide_point(touch.x, touch.y):
self.parent.remove_widget(self)
return True
return super(RemovableButton, self).on_touch_down(touch)
class AddItemWidget(Widget):
def __init__(self, **kwargs):
super(AddItemWidget, self).__init__(**kwargs)
self.count = 0
def buttonClicked(self):
self.count += 1
boxLayout = BoxLayout()
textinput = TextInput(text='Hello world'+str(self.count),size_hint_x=0.8)
deleteButton = RemovableButton(text='×',size_hint_x=0.2)
boxLayout.add_widget(deleteButton, index=1)
boxLayout.add_widget(textinput, index=1)
self.ids.box.add_widget(boxLayout, index=1)
deleteButton.bind(on_release=boxLayout.remove_widget)
class TestApp(App):
def __init__(self, **kwargs):
super(TestApp, self).__init__(**kwargs)
def build(self):
return AddItemWidget()
if __name__ == '__main__':
TestApp().run()
When the above code is executed, GUI will be launched and a line will be added by pressing the "Add Item" button.
I want to remove the line, so with the "x" button, as in the image below.
Its a little different than your other example.
For this, you need to use a different function to do the removing.
You also have to import partial like this: from functools import partial
And here is the changed AddItemWidget class:
class AddItemWidget(Widget):
def __init__(self, **kwargs):
super(AddItemWidget, self).__init__(**kwargs)
self.count = 0
def buttonClicked(self):
self.count += 1
boxLayout = BoxLayout()
textinput = TextInput(text='Hello world'+str(self.count),size_hint_x=0.8)
deleteButton = RemovableButton(text='×',size_hint_x=0.2)
boxLayout.add_widget(deleteButton, index=1)
boxLayout.add_widget(textinput, index=1)
self.ids.box.add_widget(boxLayout, index=1)
deleteButton.bind(on_release=partial(self.remove_btn, boxLayout)) # change this
def remove_btn(self, boxLayout, *args): # and add this
self.ids.box.remove_widget(boxLayout)

Kivy's labels overlapping when dynamically resizing screen, how can i keep space between them?

My code creates lots of boxlayouts (with a label) inside another boxlayout (id: box). it's ok, but when the text is too long or i resize the screen it overlaps other labels, like this Example
test.kv
<List>:
BoxLayout:
id:box
padding:50
spacing:200
orientation: 'vertical'
size_hint_y: None
height: self.minimum_height
<Item>:
Label:
id: label
size_hint_y: None
font_size:20
text_size: self.width, None
height: self.texture_size[1]
Class.py
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.scrollview import ScrollView
class Test(App):
def build(self):
list1 = []
for i in range(4): list1.append(15 * "Long Text here, Long Text here, Long Text here,")
return List(list1)
class List(ScrollView):
def __init__(self, text1, **kwargs):
super().__init__(**kwargs)
for t in text1:
self.ids.box.add_widget(Item(text = t))
class Item (BoxLayout):
def __init__(self, text = '', **kwargs):
super().__init__(**kwargs)
self.ids.label.text = text
Test().run()
I've tried some things like changing the padding, text_size, height but didn't work and it was ugly, what can i do?
I want to have a fixed, small gap between the "tweet" widgets inside of the BoxLayout (id:box)
*edited to include minimal example

Kivy - How to drag and drop items from ScrollView to another layout?

I'm attempting to drag some images arranged inside a GridLayout (which is indeed inside a ScrollView) to outer Layout.
The image to be dragged has its on_touch_down event defined, when image is clicked parent is changed from WidgetMenu to MainLayout so it can be dragged between those widgets. The current problem is when I touch the image, DragBehavior is lost.
The full code:
import kivy
kivy.require("1.9.1")
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.scrollview import ScrollView
from kivy.uix.widget import Widget
from kivy.uix.behaviors import DragBehavior
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.base import runTouchApp
from kivy.lang import Builder
Builder.load_string('''
<WidgetMenu>:
canvas.before:
Color:
rgb: 0.9,0.5,0.3
RoundedRectangle:
pos:self.pos
size: self.size
radius: [20,]
orientation: "vertical"
padding:30
ScrollView:
GridLayout:
cols:1
size_hint_y:None
row_default_height:root.height*.15
height:self.minimum_height
DragImage:
DragImage:
DragImage:
<DragImage>:
drag_rectangle: self.x, self.y, self.width, self.height
drag_timeout: 100000000
drag_distance: 0
size_hint:None,None
size:234,34
canvas:
Color:
rgb:1,0,1
Rectangle:
pos: self.pos
size: self.size
<MainLayout>:
canvas:
Color:
rgb:1,1,1
Rectangle:
size: self.size
pos: self.pos
WidgetMenu:
size_hint: 0.35,0.9
''')
class MainLayout(FloatLayout):
pass
class WidgetMenu(BoxLayout):
pass
class DragImage(DragBehavior,FloatLayout):
def on_touch_down(self,touch):
workspace = self.parent.parent.parent.parent
grid = self.parent
menu = self.parent.parent.parent
if "MainLayout" in str(workspace):
grid.remove_widget(self)
workspace.remove_widget(menu)
self.pos = Window.mouse_pos
workspace.add_widget(self)
return True
class ScrollApp(App):
def build(self):
return MainLayout()
ScrollApp().run()
Please help.
Two problems with your code are that you are not calling the super method in your on_touch_down, and placing the DragImage in the top level MainLayout changes the pos of the DragImage, thus changing its DragRectangle. That change in DragRectangle leaves the touch.pos outside of it, so DragBehavior thinks the touch is not on the DragImage.
I have fixed both those problems by calling the super method and changing the touch.pos before passing it to the super method. I also added some code to keep the DragImage in the same position relative to the mouse when it is clicked. Also added a call to self.collide_point() in order to ignore clicks not on the DragImage.
class DragImage(DragBehavior,FloatLayout):
def on_touch_down(self,touch):
if not self.collide_point(*touch.pos):
return False
workspace = self.parent.parent.parent.parent
grid = self.parent
menu = self.parent.parent.parent
if "MainLayout" in str(workspace):
grid.remove_widget(self)
workspace.remove_widget(menu)
# the following code assumes that workspace is the entire Window
self.x = Window.mouse_pos[0] - (touch.pos[0] - self.x)
self.y = Window.mouse_pos[1] - (touch.pos[1] - self.y)
workspace.add_widget(self)
touch.pos = Window.mouse_pos
return super(DragImage, self).on_touch_down(touch)

Categories

Resources