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.
Related
I want to change the MDToolbar left_action_item icon color. Its defaulting to white, but now i want to change it to red. Whats the simplest way to do this? I've tried almost everything (text_color, bg_color, etc) all to no avail.
You cannot change the color of the icons in the toolbar.
Using specific_text_color: 1,0,1,1 you can change the color of the text inside the toolbar. It changes both the text AND the icon. I have no idea how to change only the icon. Maybe this helps.
At the moment iam having trouble changing the icon color of a OneLineIconListItem. I think its the same constraint we are encountering?
In situations like these, I recommend searching the KivyMD repository for the relevant widget class, and then poking around to see how it's being defined, what the relevant IDs are, and so forth. For instance, this line in toolbar.py seems to define the icons in the toolbar:
def update_action_bar(self, action_bar, action_bar_items):
#...
action_bar.add_widget(
MDIconButton(
icon=item[0],
on_release=item[1],
opposite_colors=True,
text_color=self.specific_text_color,
theme_text_color="Custom",
)
)
#...
Here we learn that the toolbar's icons are of class MDIconButton, and they have a text_color color attribute which seems to be setting the color.
Looking at where the function above is called, we see that these icons are being added as widgets to self.ids["left_actions"] and self.ids["right_actions"] respectively:
def on_left_action_items(self, instance, value):
self.update_action_bar(self.ids["left_actions"], value)
def on_right_action_items(self, instance, value):
self.update_action_bar(self.ids["right_actions"], value)
Knowing all that, now in our own code, say in the build() function of our MainApp, we can access and modify the attribute:
def build(self):
# ...
# get the root widget
self.root = root = Builder.load_file('root.kv')
# get toolbar
toolbar=root.ids.toolbar
# get the icons on the right
action_items = toolbar.ids.right_actions.children
# loop over the icons
for item in action_items:
# change the color
item.text_color=(1,0,0,1) # red
This doesn't need to be in build(), it just needs to be somewhere you can access the toolbar widget by its ID somehow.
Using both md_bg_color: app.theme_cls.primary_color and text_color: rgba('#F0F0F0') allowed me to change the color of icon buttons within MDToolbar.
I have a kivy program that has a ‘sqlite’ database containing details of a golf competition which displays information & warning messages via a standard Pop-up function - pop_mess()
For testing purposes I open an alternative ‘testing’ database in a function – set_test_db(). When this is done I would like to change the background colour of the pop-up to warn the user that the main database is not being used.
I have tried to do this (code at end of 'set_test_db'). However although the routine retrieves the current background colour correctly it will not change it.
Can someone point me in the direction of a solution. The coding method works fine for changing the text properties of a widget. (similar questions usually refer to text properties). I have used both list & tuple for colour values.
In .ky file
<CustomPopup>:
popup_message: popup_message
size_hint: .98, .75
id: popup_id
title: "Casual and Ancient"
title_align: 'center'
title_color: 1,.5,.3,1
BoxLayout:
id: contentbox
orientation: 'vertical'
TextInput:
id: popup_message
color: .3,.4,1.0,1
background_color: [.7,1.0,.2,1]
text: "text message goes here"
font_size: 16
font_name: 'RobotoMono-Regular'
…..
CaaRoot:
<CaaRoot>:
orientation: 'lr-tb'
padding: 10
etc
...
in main.py
(self = CaaRoot)
...
def pop_mess(self,message):
p_up=CustomPopup()
lab=p_up.ids['popup_message']
lab.text=message
p_up.open()
...
def set_test_db(self):
# on button in CaaRoot
# open test data base instead of real
if self.db != None:
print 'Cannot Open Test Data Base - already running'
else:
# open ‘test’ database
# set background colour of popup to warn user.
fn='/home/.... caatestdb.db
self.op_db(fn)
self.testing='TEST database'
p_up=CustomPopup()
lab=p_up.ids['popup_message']
x=lab.background_color
new_col=[.9,.3,.3,1]
lab.background_color=new_col
self.pop_mess('color set to : '+str(new_col)+ ' was : '+str(x))
the popup displays
color set to : [0.9, 0.3, 0.3, 1] was : [0.7, 1.0, 0.2, 1]
(with background still set to [0.7, 1.0, 0.2, 1])
Have a look at kivymd, which solves issues like that very well. It is basically a very elegant template for kivy based on Google's Material Design.
For me it makes all the coloring stuff and appearance in general much easier, especially as I am not a designer but a programmer.
The Demo has also a very complete sets of examples including popups.
On further investigation of my problem I came across the similar question (Changing Background of Kivy Popup )
The fact that the solution given to that was “non-intuitive” involving the properties of the ‘inherited’ Canvas element(?) made me realise (remember ?) than some widgets do not have the full range of modifiable properties.
The 'background_color' property that I was retrieving was a property inherited from the Canvas which cannot be easily changed.
I have avoided the problem by creating a ‘popup’ widget whenever I wish to use it rather than modifying an existing widget. I still cannot (simply) change the background_color of the popup but I can change the background color of the parent element so that the user’s eye immeditely sees something different. I can also change the text color when calling the popup message function.
def pop_mess(self,message,colour=(.9,.6,.9,1)):
#
# text color can ve overridden by the 'colour' parameter
# self.backc can be set as required to set the color behind the
# popup depending on circumstances
popup=Popup(title='Casual & Ancient', \
content=Label(text=message,color=colour,\
font_name = 'RobotoMono-Regular'),\
size_hint=(.98,.75),background_color=self.backc)
popup.open()
I'm not completely sure that the analysis is correct or that there may be some side-effects but it seems to work ok (and was somewhat simpler to code)
I am working on developing an application using Kivy. I am using Kivy ActionBar for creating a menu bar for my application.
Kindly refer to the image attached
I want to remove the Kivy icon and move the other options(file / edit) to the left. Please find the snippet of my code.
menuAcBar = ActionBar(pos_hint={'top': 1.3})
menuAcView = ActionView()
menuAcBar.add_widget(menuAcView)
menuAcPrevious = ActionPrevious(with_previous=False)
menuAcView.add_widget(menuAcPrevious)
menuAcView.add_widget(ActionButton(text="File"))
menuAcView.add_widget(ActionButton(text="Edit"))
menuAcView.add_widget(ActionButton(text="Documents"))
menuAcView.add_widget(ActionButton(text="help"))
self.add_widget(menuAcBar)
Right on ActionPrevious you can set app_icon. It's a little bit lower in docs. You can set app_icon_width/height for size of the icon or even remove it with app_icon='', but it'll leave white rectangle instead of a "transparent". Leave app_icon be and set only width and height to make it invisible.
The ĄctionPrevious has ActionItem's minimum_width property, therefore you need to change it like this:
menuAcPrevious = ActionPrevious(with_previous=False,
app_icon=<your_image>,
app_icon_width=1,
app_icon_height=0,
minimum_width=10,
size_hint_x: None)
Edit:
It seems that ActionPrevious leaves additional unused space even if title='' and minimum_width=1 and you can't access the damn thing through children because it's unregistered, therefore the only thing I came up with is resizing it so you won't see it anymore:
ActionPrevious(
size_hint_x = None,
width = 0,
app_icon_width = 0.1,
with_previous = False)
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("")
...
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