Kivy dynamic background loading not functioning - python

The goal is a screen which uses one of several images (randomly chosen upon each screen load) as a background.
The app contains the following:
class AnswerScreen(Screen):
bkgd = ""
def choose_bkgd(self):
self.bkgd = "{}.jpg".format(random.randint(0,8))
My kv file contains the following:
<AnswerScreen>
on_pre_enter: root.choose_bkgd()
canvas.before:
Rectangle:
pos: self.pos
size: self.size
source: root.bkgd
Unfortunately the background is always just a solid white.
I've added a print call to choose_bkgd(), and it always prints an acceptable file name, and I've also tried using on_enter: but there is no change. If I replace source: with a file name instead of root.bkgd the image displays correctly. This leads me to believe that the background is being generated before the function is being called to set the bkgd variable, but this confuses me as I thought the whole point of on_pre_enter was to execute code prior to the loading of the screen. The kivy docs haven't cleared this up for me. Any help is greatly appreciated.

Make bkgd a kivy property. This is essential to be able to bind to it and have things automatically update when it changes.
from kivy.properties import StringProperty
class AnswerScreen(Screen):
bkgd = StringProperty("")
...

Related

Error while drawing kivy rectangle using canvas

I need some help with kivy,i am pretty new to kivy and I made a class to to draw a rectangle as a background.
I am pretty sure I did everything correctly but there is an error,so here is my code
The .py file
import kivy
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.textinput import TextInput
class Background(Widget):
pass
class MY_browser(App):
def build(self):
return Background
MY_browser().run()
The .kv file
Floatlayout:
Background:
canvas.before:
Rectangle:
pos: self.pos
size: self.size
I have tried a lot of things but no difference so if anyone can help I would really appreciate it
A couple problems:
In your build() method, the return Background is returning a class, but the build() method is expected to return a Widget instance. Perhaps this should be return Background().
In your kv, the indentation is incorrect. All indentations should be a multiple of the same number of spaces (typically 4). The indentation of Background is too large.
Your kv does not provide a rule for Background, so when Background() is returned, it will simply be an empty Widget. If you want to return Background(), you should have a <Background>: rule in the kv.
If your kv file is named my_browser.kv, then you do not need a build() method for your App class at all.
If your kv file is not named as above, then your build method can be return Builder.load_file("kv file name"), where kv file name is replaced by the correct name of your kv file.
The Floatlayout in your kv is misspelled. It should be FloatLayout.

Static background in kivy

I was wondering if there is a way to get a static background in a Kivy application with a screen manager. With static, I mean that the background remains as it is, even when switching screens. I am using a .kv file for the layout. I'd guess it has something to do with the placement order within a .kv file.
Thanks!
You can use a float layout as the root widget in every screen and add to that float layout the image and the other layout in your screen, here is a code example in kv:
Screen: # Screen 1
id: Home
FloatLayout:
Image:
source: "path to the image"
BoxLayout: # Here you put your other layout
# And here the code you had
Screen: # Screen 2
id: Another Screen
FloatLayout:
Image:
source: "path to the image"
BoxLayout: # Here you put your other layout
# And here the code you had
This is the solution that I know, it might not be perfect for you, but I will leave the other options to others...
FloatLayout:
Image:
ScreenManager:
would be enough I think but be careful, if the transition of ScreenManager is ShaderTransition(or a sub-class thereof), it doesn't respect the pixels in the background, so the animation during the transition may not work properly.

Dynamically deleting and loading Screen object in Kivy

I am currently developing an embedded system with kivy.
Therefore, I found that if I make many screens, it slows down the program a lot.
Is there a good way to dynamically control screens so it does not slow down?
For instance, when I have 4 screens in ScreenManager like below,
MyScreenManager:
id: myscreenmanager
transition: FadeTransition()
SCRN_LOADING:
SCRN_IDLE:
SCRN_CALCULATING:
SCRN_RESULT:
Would it be possible to:
innitially load SCRN_LOADING first.
loads SCRN_IDLE and SCRN_CALCULATING while loading.
when loading is done, remove SCRN_LOADING screen object.
loads SCRN_RESULT while calculating.
when going back to idle, remove SCRN_RESULT screen object.
I am guessing this could improve performance.
Currently, the screen lags really hard. So I might have to restart the whole project using C if I can't solve the performance issue.
Please help me out!
I suppose you could declare your screens outside kv and then add them as required in your screen manager,In your kv
MyScreenManager:
id: myscreenmanager
transition: FadeTransition()
In your Window class:
from kivy.uix.screenmanager import ScreenManager, Screen
...
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.sm = self.ids.myscreenmanager
self.loading = Screen(name='SCRNLOADING')
self.idle = Screen(name='SCRN_IDLE')
self.calc = Screen(name='SCRN_CALCULATING')
self.sm.add_widget(self.loading)
self.set_idle()
def add_scrn(self):
self.sm.add_widget(self.idle)
self.sm.add_widget(self.calc)
self.sm.remove_widget(self.loading)
Im not really used to the Clock class but I'm sure you will need it here to load your screens correctly

Kivy CheckBox Looks Like Solid Black Box (Not a Checkbox)

I am making a BoxLayout widget (orientation = 'horizontal') that contains three widgets inside of it, a label, a text box, and a check box.
thisRow = BoxLayout(orientation='horizontal')
l = Label(text='Enter plate 1:\n(Plate #)')
t = TextInput(text = 'this is a text box')
c = CheckBox()
thisRow.add_widget(l)
thisRow.add_widget(t)
thisRow.add_widget(c)
This produces the following widget (thisRow):
After the box is checked...
The rightmost black box is actually the checkbox, and works functionally, however there is no way for the user to know that it is in fact a checkbox. I would expect a smaller empty square in the middle, as is depicted in pictures here.
How do i get the traditional checkbox image (smaller empty square box)? Or generally, how can I make it more obvious that the box is a check box and not just an empty label?
Thank you
This is really interesting question and Malonge tried it in a good way. Right now(1.9.2-dev) there is still fixed size on CheckBox's well, call it a background. It's an image that Widget takes from atlas and changes if the state changes. Therefore until now there was no clear way how to do it. Here is an example. Soon on master there'll be CheckBox(color=[r,g,b,a]) option. Thanks ;)
from kivy.lang import Builder
from kivy.base import runTouchApp
from kivy.uix.boxlayout import BoxLayout
Builder.load_string('''
<CheckBoxBG>:
Label:
TextInput:
CheckBox:
canvas.before:
Color:
rgb: 1,0,0
Rectangle:
pos:self.center_x-8, self.center_y-8
size:[16,16]
Color:
rgb: 0,0,0
Rectangle:
pos:self.center_x-7, self.center_y-7
size:[14,14]
''')
class CheckBoxBG(BoxLayout):pass
runTouchApp(CheckBoxBG())
Looks like the smaller check box is hidden when the background color is black. Here is an example of a red background.
It's not ideal because I do like the black background, but I can run with it for now. If anyone knows how to do this with a black background that would be great. Thank you
Alternatively, to change your checkboxes background, you can use another image from the atlas or create images and then load them:
mycheckbox= CheckBox(
background_checkbox_normal ='tlas://data/images/defaulttheme/button_disabled'
background_checkbox_down = 'my_checkboxes_checked.png'
)
In Kivy 1.9.2.dev0 (and apparently since version 1.9.0) you can change the background image of checkboxes. By default, Kivy uses for these backgrounds from the atlas*.
background_checkbox_normal = StringProperty('atlas://data/images/defaulttheme/checkbox_off') #when the checkbox is not active.
background_checkbox_down = StringProperty('atlas://data/images/defaulttheme/checkbox_on') # when the checkbox is active.
background_checkbox_disabled_normal = StringProperty('atlas://data/images/defaulttheme/checkbox_disabled_off') #when the checkbox is disabled and not active.
background_checkbox_disabled_down = StringProperty('atlas://data/images/defaulttheme/checkbox_disabled_on') #when the checkbox is disabled and active.
You can have a look here at all the attributes :
*The atlas is a package of multiple textures that reduces the number of images loaded and speedup the application loading. You have see a preview of the atlas in Python Install Folder\Lib\site-packages\kivy\data\images\defaulttheme-0.png

Python Kivy Canvas wont Update

I am creating a 'Map editor' using 64x64 tiles. I need my canvas to update when i click and change the tile at location. I originally set the canvas with
with self.canvas:
Rectangle(source = 'image.png')
in one class, then in my touch_down class i call
with self.canvas:
Rectangle(source = 'newImage.png')
after i change my image to update it.
I have been able to get it to update but i have to create a new image each time, it seems it wont update since i already added that rectangle with that specific source image, and doesn't see that the image has been changed?
In response to Ryan P
Still nothing. I tried this
Class mypaintwidget(Widget): #This is added as a widget to my layout
def on_touch_down(self, touch):
with self.canvas:
self.rect = Rectangle(source = 'image.png')
tilepng = pil.open('64x64tile.png') #pil is Python Image Library
tilemap = pil.open('image.png')
tilemap.paste(tilepng,location)
tilemap.save('newimage.png')
self.rect.source = 'newimage.png'
It will only update ONCE. then nothing (but will still save that image, but wont show it to me.
You need to modify the Rectangle instruction (or remove and replace, but modifying is easier):
with self.canvas:
self.rect = Rectangle(source='image.png')
Then later:
self.rect.source = 'newImage.png'
As for the second part of your question, the issue is that images are cached upon loading in Kivy. So when you save newimage.png and reload it again, Kivy knows you've already loaded newimage.png. This isn't really a good design for a Kivy app.
Also, creating the Rectangle in on_touch_down means you end up creating a new Rectangle each time the widget is pressed, so you just add more and more unnecessary drawing instructions.
You may also notice that when you add multiple of these widgets to a layout, that they all render in the same place at the full size of the window. Widgets are not constrained to drawing in their area, and can draw anywhere within the app. You need to make sure the Rectangle instruction knows where to draw, by passing it size and pos parameters.
Finally, instead of saving out the image, you can just display one atop the other. This will be much more efficient and doesn't require using unique filenames or messing with the caching system.
You should definitely take a look at the Kivy language (kv) as it is much easier to use for designing widgets and laying out your app.
Here's an example of using kv to do this:
<TileWidget>:
tilesource: ''
canvas:
Color:
rgba: 1, 1, 1, 1
Rectangle:
size: self.size
pos: self.pos
source: 'image.png'
Color:
a: 1 if self.tilesource else 0
Rectangle:
size: self.size
pos: self.pos
source: self.tilesource
Now I'll explain what all of this does.
<TileWidget>:
This is a class rule, because the name is surrounded by <>. It will apply to all instances of TileWidget.
tilesource: ''
Here we are setting the value of the property tilesource. But tilesource isn't a property on the Widget class! No worries. In kv, when you assign a value to a non-existent property like this, the property will be created automatically. Since the value we're assigning to the property is a string, Kivy will realize we want this to be a StringProperty.
By creating this property, we are making it possible to affect this widget from the outside. We will use the value of this property to display an image later.
canvas:
Just like using with self.canvas: from the class in Python.
Color:
rgba: 1, 1, 1, 1
Inside a canvas block, we can add drawing instructions. We start with a Color instruction, because you don't know what the color is currently set to. This color will tint the images we display, and we don't want any tint, so we use a full white color.
Rectangle:
size: self.size
pos: self.pos
source: 'image.png'
Now we are going to render the first image. We make sure that the Rectangle's size and pos match the widget. In kv, doing this automatically creates a binding. If the widget is moved around, its size will change, and the Rectangle will update itself to match.
Color:
a: 1 if self.tilesource else 0
This time, we know what the color is set to. However, we don't want to draw anything else until an image has been set. In kv, properties will accept any Python value. This means you can use ternary expressions like this one, or function calls, or arithmetic, etc. So if self.tilesource evaluates to False (like an empty string, the default value we set above) then the a property of the color (the alpha component) will be set to 0, otherwise it will be 1.
Rectangle:
size: self.size
pos: self.pos
source: self.tilesource
Finally, we render the selected image.
Now, to create the widget itself, we just need a little bit of Python:
class TileWidget(Widget):
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
self.tilesource = '64x64tile.png'
return True
return super(TileWidget, self).on_touch_down(touch)
It's important to use collide_point when handling touches. Just like how widgets can draw anywhere in the app, they can also handle touches anywhere in the app. This makes sure that the touch is actually within the bounds of our widget before acting. Inside the collide_point block, we will return True to let Kivy know that we have handled this touch and that nothing else should process it. Otherwise, we call super() to let the default handler take over.

Categories

Resources