I am trying to dynamically change the background color of a Label in Kivy with this simple program. it is intended to produce a grid of red and black cells. However, all I get is a red cell in position (7,0) (I am printing the positions).
import kivy
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.graphics import Color, Rectangle
class LabelX(Label):
def set_bgcolor(self,r,b,g,o,*args):
self.canvas.after.clear()
with self.canvas.after:
Color(r,g,b,o)
Rectangle(pos=self.pos,size=self.size)
class MyGrid(GridLayout):
def __init__(self,cols,rows,**kwargs):
super(MyGrid,self).__init__(**kwargs)
self.cols = cols
for i in range(rows):
for j in range(cols):
l = LabelX(text=str(i)+","+str(j))
if (i*rows+j)%2:
l.set_bgcolor(1,0,0,1)
else:
l.set_bgcolor(0,0,0,1)
self.add_widget(l)
class GridApp(App):
def build(self):
g = MyGrid(8,8)
return g
if __name__=="__main__":
GridApp().run()
Any ideas on how to get a red/black checker board?
If you print self.pos using the following:
with self.canvas.after:
[...]
print(self.pos)
[...]
only the values [0, 0] are obtained, and that leads to the conclusion that all the rectangles are drawn in that position, so they are superimposed, and that is what you observe.
For example, to verify this we pass a parameter more to the function set_bgcolor () that will be the index of the loop and we will use it as a parameter to locate the position:
def set_bgcolor(self, r, b, g, o, i):
[..]
Rectangle(pos=[100*i, 100*i],size=self.size)
class MyGrid(GridLayout):
def __init__(self,cols,rows,**kwargs):
[...]
if (i*rows+j)%2:
l.set_bgcolor(1,0,0,1, i)
else:
l.set_bgcolor(0,0,0,1, i)
self.add_widget(l)
We get the following:
also if we change the size of the window the rectangle does not change either:
So if you want the Rectangle to be the bottom of the Label, the position and size of the Rectangle should be the same as the Label, so you have to make a binding with both properties between the Label and Rectangle. You must also use canvas.before instead of canvas.after otherwise the Rectangle will be drawn on top of the Label.
class LabelX(Label):
def set_bgcolor(self,r,b,g,o):
self.canvas.before.clear()
with self.canvas.before:
Color(r,g,b,o)
self.rect = Rectangle(pos=self.pos,size=self.size)
self.bind(pos=self.update_rect,
size=self.update_rect)
def update_rect(self, *args):
self.rect.pos = self.pos
self.rect.size = self.size
The advantage of my solution is that it is independent of the external widget or layout since the position and size of the Rectangle only depends on the Label.
The problem is that each labels position is (0,0) and size is (100,100) until the app is actually built. so all your canvas drawing is done at those positions and sizes. You can get the checkerboard effect by waiting until after the positions and sizes are assigned, then doing the checkerboard effect. I do this with a Clock.schedule_once:
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.graphics import Color, Rectangle
from kivy.clock import Clock
from kivy.core.window import Window
class LabelX(Label):
def set_bgcolor(self,r,b,g,o,*args):
self.canvas.after.clear()
with self.canvas.after:
Color(r,g,b,o)
Rectangle(pos=self.pos,size=self.size)
class MyGrid(GridLayout):
def __init__(self,cols,rows,**kwargs):
super(MyGrid,self).__init__(**kwargs)
self.cols = cols
for i in range(rows):
for j in range(cols):
l = LabelX(text=str(i)+","+str(j))
l.rowcol = (i,j)
self.add_widget(l)
class GridApp(App):
def build(self):
self.g = MyGrid(8,8)
Window.bind(size=self.checkerboard)
return self.g
def checkerboard(self, *args):
for l in self.g.children:
count = l.rowcol[0] + l.rowcol[1]
if count % 2:
l.set_bgcolor(1, 0, 0, 1)
else:
l.set_bgcolor(0, 0, 0, 1 )
if __name__=="__main__":
app = GridApp()
Clock.schedule_once(app.checkerboard, 1)
app.run()
Related
import kivy
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.clock import Clock
from kivy.graphics import Rectangle
from kivy.core.image import Image as CoreImage
from kivy.core.window import Window
class Background(Widget):
def __init__(self, **kw):
super(Background, self).__init__(**kw)
with self.canvas:
texture = CoreImage('space.png').texture
texture.wrap = 'repeat'
self.rect_1 = Rectangle(texture=texture, size=self.size, pos=self.pos)
Clock.schedule_interval(self.txupdate, 0)
def txupdate(self, *l):
t = Clock.get_boottime()
self.rect_1.tex_coords = -(t * 0.001), 0, -(t * 0.001 + 10), 0, -(t * 0.001 + 10), -10, -(t * 0.001), -10
class CosmicPolygons(App):
def build(self):
return Background(size=Window.size)
if __name__ == "__main__":
CosmicPolygons().run()
I've tried many different ways and attempts in order to create a scrolling background in Kivy. This was the best method I could find as it was the only one that didn't crash. But I'm pretty sure it's still outdated as it did not work as intended, in addition to distorting my png greatly.
If anyone has any ideas on how to achieve this, let me know. Thanks in advance.
Image of current app:
Space.png:
Here is another approach that just creates a list of Images and moves them across the screen:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.image import Image
from kivy.clock import Clock
from kivy.core.window import Window
class BgImage(Image):
pass
Builder.load_string('''
<BgImage>:
source: 'stars.png'
allow_stretch: True
size_hint: None, 1
width: self.image_ratio * self.height
''')
class Background(FloatLayout):
def __init__(self, **kwargs):
super(Background, self).__init__(**kwargs)
self.deltax = 3 # sets speed of background movement
self.bg_images = [] # list of Images that form the background
Clock.schedule_once(self.set_up_bg)
def set_up_bg(self, dt):
# create BgImages and position them to fill the Background
self.start_x = None
pos_x = -1
while pos_x < self.width:
img = BgImage()
if self.start_x is None:
# starting position of first Image is just off screen
self.start_x = -self.height * img.image_ratio
pos_x = self.start_x
img.pos = (pos_x, 0)
self.bg_images.append(img)
self.add_widget(img)
# calculate starting position of next Image by adding its width
pos_x += self.height * img.image_ratio
# start moving the background
Clock.schedule_interval(self.update, 1.0/30.0)
def update(self, dt):
for img in self.bg_images:
img.x += self.deltax
if img.x > self.width:
# this Image is off screen, move it back to starting position
img.x = self.start_x
class CosmicPolygons(App):
def build(self):
return Background(size=Window.size)
if __name__ == "__main__":
CosmicPolygons().run()
What I am trying to do is continuously generate new cloud images onto the kivy window -which I have done successfully-, but now I want them to continuously move to the left and off the screen to give the "sky" environment a moving effect. Though I haven't been able to change the images' positions. Any suggestions as to how I could make it work?
from kivy.app import App
from kivy.properties import Clock
from kivy.uix.widget import Widget
from kivy.graphics.context_instructions import Color
from kivy.uix.image import Image
from kivy.core.window import Window
import random
class MainWidget(Widget):
state_game_ongoing = True
clouds = []
def __init__(self, **kwargs):
super().__init__(**kwargs)
Clock.schedule_interval(self.init_clouds, 1 / 10)
def init_clouds(self, dt):
x = random.randint(-100, self.width)
y = random.randint(-100, self.height)
cloud = Image(source="images/cloudbig.png",
pos=(x, y))
self.add_widget(cloud)
class BalloonGameApp(App):
def build(self):
Window.clearcolor = (.2, .6, .8, 1)
xd = MainWidget()
return xd
BalloonGameApp().run()
I tried adding the clouds into a list using .append(), and calling each cloud and changing its position in a for loop via
clouds = []
def init_clouds(self, dt):
x = random.randint(-100, self.width)
y = random.randint(-100, self.height)
cloud = Image(source="images/cloudbig.png",
pos=(x, y))
self.add_widget(cloud)
clouds.append(cloud)
x += 100
y += 100
for cloud in self.clouds:
cloud.pos(x, y)
I also tried the same method but in an "update" function that was on loop via Clock.schedule_interval() kivy function, which also didn't work the error I usually get is
TypeError: 'ObservableReferenceList' object is not callable
Any help would be appreciated, thanks in advance!
You need to check all Image's Parent. For more soft move lets create another Clock:
Clock.schedule_interval(self.move_clouds,1/100)
Now lets edit this function:
def move_clouds(self,*args):
out_cloud_list = [] #If cloud not in screen remove:
for child in self.children:
if child.pos[0] >= self.width:
out_cloud_list.append(child)
else:
child.pos[0] += 5
for cloud in out_cloud_list:
self.remove_widget(cloud)
I have tried to make 2d array that store rectangle(with size=(1,1)) and color object, so it work like a new canvas on a kivy canvas, but it's laggy when the first time it create array of rectangle even when i just put 100x100 pixel, here's the code:
import kivy
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.core.window import Window
from kivy.graphics import Color, Line, Point, Rectangle
from kivy.clock import Clock
class Touch(Widget):
def __init__(self, **kwargs):
super(Touch, self).__init__(**kwargs)
self.new_window_size= None
self.timer= 0
Clock.schedule_interval(self.update_screen, 1/30)
def update_screen(self, *args):
with self.canvas:
self.old_window_size= self.new_window_size
self.new_window_size= Window.size
if(self.old_window_size!= self.new_window_size):
#i set 100x100 just to test
self.w= 100 #self.new_window_size[0]
self.h= 100 #self.new_window_size[1]
self.color= []
self.pixel= []
for x in range(self.w):
temp_1= []
temp_2= []
for y in range(self.h):
print(x, y)
temp_1.append(Color(1, 1, 1, 1))
temp_2.append(Rectangle(pos= (x, y), size= (1, 1)))
self.color.append(temp_1)
self.pixel.append(temp_2)
else:
#update color
self.color[self.timer][24].rgba= (0, 0, 0, 1)
print(self.timer)
self.timer+= 1
class MyApp(App):
def build(self):
return Touch()
MyApp().run()
is there a proper way to change single pixel in kivy canvas class?
To change just one pixel, you can use a Rectangle with size (1,1). Most easily done using kv:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.widget import Widget
class Touch(Widget):
pass
kv = '''
<Touch>:
canvas:
Color:
rgba: 1,0,0,1 # the new pixel color
Rectangle:
pos: 200, 200 # The pixel that you want to set
size: 1,1 # indicate that just one pixel is changed
'''
class MyApp(App):
def build(self):
Builder.load_string(kv)
return Touch()
MyApp().run()
I have made a square shaped grid of labels using Gridlayout. Now i want to add a background color the labels(each having different rectangles). I tried to do this by the following code.
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.gridlayout import GridLayout
from kivy.graphics import Rectangle, Color
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.label import Label
class MyGrid(FloatLayout):
def __init__(self,**kwargs):
super(MyGrid,self).__init__(**kwargs)
self.grid=GridLayout()
self.grid_size=4
self.grid.cols=self.grid_size
for k in range(self.grid_size):
for i in range(self.grid_size):
with self.grid.canvas:
Rectangle(size=(100,100),pos=(k*160+100,i*160+100),source="52852.JPG")
for h in range(self.grid_size):
for j in range(self.grid_size):
self.grid.add_widget(Label(text="labwl"+str(h)+str(j),size=(100,100),pos=(h*160+100,j*160+100)))
self.add_widget(self.grid)
class GameApp(App):
def build(self):
return MyGrid()
if __name__ == '__main__':
GameApp().run()
In this code if I do not specify "self.grid.cols" it generates a warning in the python console and also when the window is turned to full screen mode the rectangles from canvas retain there original size and position but the labels do not. I want to get the labels in front of the rectangles of canvas and they should also retain the size of the screen as specified. Moreover if I change the "self.grid.size" to any other number it should make the grid of labels of that length and corresponding number of canvas too. I tried float layout for this purpose also but it was of no help. The canvas rectangles and labels should fit in the window whatever the size of window has. It would be better if I can get the solution to above problem written in python file(not in .kv file). If you know any other solution to this problem or any other widget please let me know. Like for button widget we can specify the background color and text also your can add any of that widget which will do above task. You should replace the "source" in the rectangle canvas to any known image file. I hope you understand. If you do not please do let me know. :)
Setting the Widgets to not change size or pos is the easiest solution. Basically just use size_hint=(None, None) and don't use the GridLayout:
from kivy.app import App
from kivy.graphics import Rectangle
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.label import Label
class MyGrid(FloatLayout):
def __init__(self,**kwargs):
super(MyGrid,self).__init__(**kwargs)
self.grid_size=4
for k in range(self.grid_size):
for i in range(self.grid_size):
with self.canvas:
Rectangle(size=(100,100),pos=(k*160+100,i*160+100),source="52852.JPG")
for h in range(self.grid_size):
for j in range(self.grid_size):
self.add_widget(Label(text="labwl"+str(h)+str(j),size=(100,100),pos=(h*160+100,j*160+100), size_hint=(None, None)))
class GameApp(App):
def build(self):
return MyGrid()
if __name__ == '__main__':
GameApp().run()
To make the Rectangles and Labels change pos and size is a bit more complex. In the modified version of your code below, I keep lists of the Labels and the Rectangles, and bind the adjust_common_size() method to run whenever the size of MyGrid changes. That method then adjusts the size and pos of the Labels and Rectangles to match:
from kivy.app import App
from kivy.properties import ListProperty
from kivy.graphics import Rectangle
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.label import Label
class MyGrid(FloatLayout):
common_size = ListProperty((100, 100))
def __init__(self,**kwargs):
super(MyGrid,self).__init__(**kwargs)
self.grid_size=4
self.rects = []
self.labels = []
for k in range(self.grid_size):
one_row = []
self.rects.append(one_row)
for i in range(self.grid_size):
with self.canvas:
one_row.append(Rectangle(size=self.common_size,pos=(k*160+100,i*160+100),source="52852.JPG"))
for h in range(self.grid_size):
one_row = []
self.labels.append(one_row)
for j in range(self.grid_size):
label = Label(text="labwl"+str(h)+str(j),size=self.common_size,pos=(h*160+100,j*160+100), size_hint=(None, None))
one_row.append(label)
self.add_widget(label)
self.bind(size=self.adjust_common_size)
def adjust_common_size(self, instance, new_size):
self.common_size = (new_size[0] * 0.9 / self.grid_size, new_size[1] * 0.9 / self.grid_size)
for k in range(self.grid_size):
for i in range(self.grid_size):
adjusted_pos = (k * new_size[0] / self.grid_size, i * new_size[1] / self.grid_size)
rect = self.rects[k][i]
label = self.labels[k][i]
label.size = self.common_size
label.pos = adjusted_pos
rect.size = self.common_size
rect.pos = adjusted_pos
class GameApp(App):
def build(self):
return MyGrid()
if __name__ == '__main__':
GameApp().run()
Using a ListProperty for the common_size is not necessary, but would be handy if you decide to use kv.
This is an interesting question. Here is a better way to make the Rectangle and the Label match. The code below uses the GridLayout, but defines MyLabel to include its own Rectangle and to keep its Rectangle matched in pos and size:
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.graphics import Rectangle
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.label import Label
class MyLabel(Label):
def __init__(self, **kwargs):
super(MyLabel, self).__init__(**kwargs)
with self.canvas.before:
self.rect = Rectangle(size=self.size,pos=self.pos,source="52852.JPG")
self.bind(size=self.adjust_size)
self.bind(pos=self.adjust_pos)
def adjust_pos(self, instance, new_pos):
self.rect.pos = new_pos
def adjust_size(self, instance, new_size):
self.rect.size = new_size
class MyGrid(FloatLayout):
def __init__(self,**kwargs):
super(MyGrid,self).__init__(**kwargs)
self.grid=GridLayout()
self.grid_size=4
self.grid.cols=self.grid_size
for h in range(self.grid_size):
for j in range(self.grid_size):
self.grid.add_widget(MyLabel(text="labwl"+str(h)+str(j),size=(100,100),pos=(h*160+100,j*160+100)))
self.add_widget(self.grid)
class GameApp(App):
def build(self):
return MyGrid()
if __name__ == '__main__':
GameApp().run()
With this approach, you don't have to create the Rectangles in the MyGrid at all, since each Label creates its own.
This is my first post here, but I will try to be as detailled as I can.
So my application is in python, and involves a grid in kivy, where each element in the grid is supposed to contain 4 additional widgets and possibility for a fifth. The four additional widgets are supposed to be in a cross shape at the edges and the fifth in the middle.
Problem is, whenever I add a sub widget it lands in the bottom left corner on position 0,0 of the main window
So far so good. Right now I am just trying to get even one widget inside another widget to display correctly.
Heres what I attempted:
<GridCell#Label>
BoxLayout:
orientation:'horizontal'
Button:
text:'One'
size:10,10
size_hint:None,None
Building a .kv file for my app, where I would put a box layout inside of it and then a button.
Also I tried the following class configuration:
class GridCell(Label):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.root = FloatLayout()
self.button = Button(text="test")
self.button.x = self.root.x
self.button.center_y = self.root.center_y
self.root.add_widget(self.button)
self.add_widget(self.root)
Also did not work.
I am adding the grid cells by just calling .add on the grid with a newly created widget for each iteration of a for loop.
All the child widgets are apparently created, but they all land in the bottom left corner!
This is the whole code of the gui right now:
import kivy
import World
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.graphics import Color, Rectangle
kivy.require('1.10.0')
class GridCell(Label):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.root = FloatLayout()
self.button = Button(text="blargh")
self.button.x = self.root.x
self.button.center_y = self.root.center_y
self.root.add_widget(self.button)
self.add_widget(self.root)
def on_size(self, *args):
self.canvas.before.clear()
if self.id is "cliff":
with self.canvas.before:
Color(249 / 255, 6 / 255, 6 / 255, 0.3)
Rectangle(pos=self.pos, size=self.size)
if self.id is "goal":
with self.canvas.before:
Color(6 / 255, 6 / 255, 249 / 255, 0.3)
Rectangle(pos=self.pos, size=self.size)
if self.id is "start":
with self.canvas.before:
Color(11 / 255, 174 / 255, 6 / 255, 0.3)
Rectangle(pos=self.pos, size=self.size)
if self.id is "normal":
with self.canvas.before:
Color(119 / 255, 115 / 255, 115 / 255, 0.3)
Rectangle(pos=self.pos, size=self.size)
class GameGridApp(App):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.grid = GridLayout(cols=8, rows=5)
self.load_into()
def load_into(self):
world = World.World(8, 5)
world.build_gamegrid()
for cell in world.gamegrid:
name = str(cell.name)
grid_cell = GridCell()
grid_cell.text = name
if cell.start:
grid_cell.id = "start"
if cell.goal:
grid_cell.id = "goal"
if cell.cliff:
grid_cell.id = "cliff"
if cell.field:
grid_cell.id = "normal"
self.grid.add_widget(grid_cell)
def build(self):
return self.grid
customLabel = GameGridApp()
customLabel.run()
I may give an idea , that create a 'subgrids' object and a 'main grid' object that contain the 'subgrids'. These two objects would be GridLayout objects.
Here is a simple example in python2.7 :
import kivy
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
class SubGrids(GridLayout):
def __init__(self):
GridLayout.__init__(self, cols=3, rows=3);
self.add_widget(Label(text='1st'));
self.add_widget(Label(text=''));
self.add_widget(Label(text='2nd'));
self.add_widget(Label(text=''));
self.add_widget(Label(text='3rd'));
self.add_widget(Label(text=''));
self.add_widget(Label(text='4th'));
self.add_widget(Label(text=''));
self.add_widget(Label(text='5th'));
class Grids(GridLayout):
def __init__(self):
GridLayout.__init__(self, cols=2, rows = 2);
self.add_widget(SubGrids());
self.add_widget(SubGrids());
self.add_widget(SubGrids());
self.add_widget(SubGrids());
class Example(App):
def build(self):
return Grids()
if __name__ == '__main__':
x = Example();
x.run();
Hope this gives an idea.