kivy gridlayout child position after resizing window - python

im new to kivy , i'm trying to make an 8puzzle game, my problem is , after moving the numbers(using Buttons or labels) in a gridlayout (using animation calss to move the buttons) there is no problem till resizing the window ! after resizing window every button or label will be in it's initial position :-/
so,why gridlayout children come back to the initial position after ressizing the window ?
how can i fix this ?
python code :
class Cubes(Button):
#value=StringProperty('0')
pos_i=NumericProperty(0)
pos_j=NumericProperty(0)
class Puzzle(GridLayout):
def __init__(self,**kwargs):
super(Puzzle,self).__init__()
self._keyboard = Window.request_keyboard(None,self)
print("Tsfsdfsdfdsfsdfdsf")
if not self._keyboard:
return
game =[
[1,2,3],
[4,5,6],
[7,8,"-"]
]
for i in range (3):
for j in range (3):
self.add_widget(Cubes(text=str(game[i][j]),pos_i=i,pos_j=j))
self._keyboard.bind(on_key_down=self.move)
self.wait=False
self._index=-1
self._solution=[]
def move(self, keyboard, keycode, text,modifires):
pos=-1
for i in range(9):
if self.children[i].text=="-":
pos=self.children[i].pos
self._index=i
#print (self.children[self._index].pos)
if keycode[1]=="up":
if Expand(Node(self.generatePuzzleTable())).CanGoUp():
for m in range(9):
if self.children[m].pos_i==self.children[self._index].pos_i-1:
if self.children[m].pos_j==self.children[self._index].pos_j:
self.replace(self.children[m].pos,self.children[self._index].pos,m)
break
if keycode[1]=="right":
if Expand(Node(self.generatePuzzleTable())).CanGoRight:
for m in range(9):
if self.children[m].pos_i==self.children[self._index].pos_i:
if self.children[m].pos_j==self.children[self._index].pos_j+1:
self.replace(self.children[m].pos,self.children[self._index].pos,m)
break
if keycode[1]=="down":
if Expand(Node(self.generatePuzzleTable())).CanGoDown():
for m in range(9):
if self.children[m].pos_i==self.children[self._index].pos_i+1:
if self.children[m].pos_j==self.children[self._index].pos_j:
self.replace(self.children[m].pos,self.children[self._index].pos,m)
break
print (keycode[1])
if keycode[1]=="enter":
self.ans()
if keycode[1]=="left":
if Expand(Node(self.generatePuzzleTable())).CanGoLeft():
for m in range(9):
if self.children[m].pos_i==self.children[self._index].pos_i:
if self.children[m].pos_j==self.children[self._index].pos_j-1:
self.replace(self.children[m].pos,self.children[self._index].pos,m)
break
if keycode[1]=="spacebar":
print ("what the fuck ?!? ",keycode[1])
self.solve()
def replace(self,pos,pos1,NodeIndex):
anime=Animation(pos=(float(pos[0]),float(pos[1])),d=0.2,t="out_cubic")#.start(self.children[0])
anime.start(self.children[self._index])
anime2=Animation(pos=(float(pos1[0]),float(pos1[1])), d=0.2,t="out_cubic")#.start(self.children[1])
anime2.start(self.children[NodeIndex])
ti=self.children[self._index].pos_i
tj=self.children[self._index].pos_j
self.children[self._index].pos_i,self.children[self._index].pos_j=self.children[NodeIndex].pos_i,self.children[NodeIndex].pos_j
self.children[NodeIndex].pos_i,self.children[NodeIndex].pos_j=ti,tj
kivy code :
<Cubes>:
background_color: 1,1,1,1
<Puzzle>:
cols: 3
spacing: 2
size: (300, 300)
BoxLayout:
canvas.before:
Color:
rgb: (0.7, 0.3, .4)
Rectangle:
pos: (self.x, self.y)
size: (self.width, self.height)
size: (600, 600)
AnchorLayout:
Puzzle:
center_x: root.center_x
center_y: root.center_y*2/3
in this image i moved the dash button to top left , but i resize the window
it will be in it's initial position (bottom right)

GridLayout (or any Layout for that matter) is responsible for positioning its children widget so on each re-size (and other meaningful events) the layout will position the widgets from scratch (this is the behavior that annoys you).
In the case of GridLayout, what matters is the order of the children of the grid...
To fix your issue you have two two options:
1) after your animation is complete swap the "-" widget with other one on the grid children list.
2) use something like https://github.com/inclement/sparsegridlayout that lets you specify what is the (i,j) for each widget(grid entry)
I hope this makes things a bit more clear

Related

How to make Canvas vertex instructions relative to a widget in Kivy

I just started learning Kivy and I was trying to understand how the Canvas instructions are affected on resizing of the window of the kivyApp.
In the Kivy documentation it is mentioned that -
Note
Kivy drawing instructions are not automatically relative to the position or size of the widget. You, therefore, need to consider these factors when drawing. In order to make your drawing instructions relative to the widget, the instructions need either to be declared in the KvLang or bound to pos and size changes.
The example which follows, shows how to bind the size and position of a Rectangle instruction to the size and position of the widget in which it is being drawn. Therefore the size and the position changes proportionally when the window is resized.
But, how can I do the same for somthing like a Bezier instruction, which uses points.
I have a custom widget HangManFig1 which extend the Widget class, which is defined in KVlang like this:
<HangManFig1>:
canvas:
Line:
points: (150, 100, 150, 700)
width: 10
Line:
points: (150, 700, 600, 700)
width: 10
Line:
points: (150, 600, 250, 700)
width: 10
Line:
points: (600, 700, 600, 600)
width: 3
Ellipse:
pos: (550, 500)
Line:
bezier: (610, 510, 630, 400, 570, 350)
width: 10
Line:
bezier: (570, 350, 510, 370, 450, 270)
width: 10
Line:
bezier: (570, 350, 600, 300, 550, 200)
width: 10
Line:
bezier: (610, 480, 530, 430, 500, 430)
width: 10
Line:
bezier: (610, 480, 630, 500, 680, 390)
width: 10
I use this widget in a Screen, in the following manner:
#:import HangManFig1 figures.hangmanfig1
<MainScreen>:
name: '_main_screen_'
BoxLayout:
RelativeLayout:
size_hint_x: 0.7
HangManFig1:
RelativeLayout:
size_hint_x: 0.3
Button:
text: 'Play'
pos_hint: {'x': 0.1, 'y': 0.80}
size_hint: (0.8, 0.1)
on_release:
root.manager.transition.direction = 'left'
root.manager.current = '_game_screen_'
Button:
text: 'Practice'
pos_hint: {'x': 0.1, 'y': 0.60}
size_hint: (0.8, 0.1)
on_release:
root.manager.transition.direction = 'left'
root.manager.current = '_game_screen_'
Button:
text: 'Share'
pos_hint: {'x': 0.1, 'y': 0.40}
size_hint: (0.8, 0.1)
on_release:
root.manager.transition.direction = 'left'
root.manager.current = '_share_screen_'
Button:
text: 'Credits'
pos_hint: {'x': 0.1, 'y': 0.20}
size_hint: (0.8, 0.1)
on_release:
root.manager.transition.direction = 'left'
root.manager.current = '_credits_screen_'
When I am resizing the window, I see that although the Buttons are being positioned correctly, but not HangManFig1.
Is there a way, in which I can bind the size of this widget to that of the Parent Widget so that it is positioned correctly even when the Window size changes?
While you used RelativeLayout to make the coordinates of your instructions relative to the position of your widget, it doesn't do anything regarding its size.
As you hardcoded all the positions by numeric values, you'll need a way to scale these values relative to the size of your widget, and you have to consider what you want to happen regarding the width of your lines in this situation, should it grow relative to the size of the widget as well? linearly? Something else? Depending on what you want various possibility exist.
The easiest starting point, IMHO, would be to use a Scale instruction, to set all the canvas instructions relative to the size of your widget, over the size you used to design your current hangman.
<HangManFig1>:
h: 600
w: 800 # just guessing the size you used to design it, adjust as needed
canvas:
PushMatrix:
Scale:
xy: self.width / self.w, self.height / self.h
Line:
points: (150, 100, 150, 700)
width: 10
Ellipse:
pos: (550, 500)
... # etc, all the other instructions remain unchanged
PopMatrix: # restore the initial canvas so it doesn't affect other instructions after your drawing
If that's not enough for you, because you want to keep the width of the line constant for example, you could either not do it with a Scale instruction, but instead have a function that takes the size of your widget and a set of coordinates as input, and returns the value relative to that size:
def rscale(size, *args):
w, h = size
ws = w / 800 # adjust accordingly, as in the first example
hs = h / 600
return (x / (ws if i % 2 else hs) for i, x in enumerate(args))
This function could be used like this.
Line:
points: rscale(self.size, 150, 100, 150, 700)
And if you want something more sophisticated, like preserving the aspect ratio of your hangman, while staying in the boundaries of your size, you could adjust accordingly to something like:
def rscale(size, *args):
w, h = size
scale = min(w / 800, h / 600) # pick the smallest scale of the two
return (x / s for x in args)
Yes, you can. It takes a bit of work, but instead of using explicit coordinates for the Canvas Lines, use values that are based on the size of the HangManFig1 object. For example, the first Line of your drawing could be something like:
<HangManFig1>:
canvas:
Line:
points: (root.width*0.1, root.height * 0.1, root.width * 0.1, root.height * 0.8)
width: 10
I've got:
class ourLine(Line):
widget=None
vLines=[]
firstime=True
def __init__(self,relto=Window,**kwargs):
self.kwargs=kwargs
if not self.widget:
print('you should inherit a new class with \'widget\' attr')
super(ourLine,self).__init__(**kwargs)
W, H = Window.width, Window.height
self.rBezier=kwargs['bezier']
self.vBezier=[]
c=0
for p in self.rBezier:
c+=1
if not c%2:
self.vBezier.append(p/H)
else:
self.vBezier.append(p/W)
self.__class__.vLines.append(self)
del self.kwargs['bezier']
def deAbstractor(self):
W, H = self.__class__.widget.width, self.__class__.widget.height
_vBezier=[]
c=0
with self.__class__.widget.canvas:
for p in self.vBezier:
c+=1
if not c%2:
_vBezier.append(p*H)
else:
_vBezier.append(p*W)
Line(bezier=_vBezier, **self.kwargs)
def dyna(c,w,s):
print(w)
for l in c.vLines:
l.__class__.widget.canvas.clear()
for l in c.vLines:
l.deAbstractor()
def activate(c):
c.widget.bind(size=lambda w,s: myLine.dyna(myLine,w,s))
This may be a little messy and can contain something unnecessary. That's because, first I wanted to make this more advanced. But then it went a little crazy. But still it is a good way to do vector lines. It supports width and other properties you give in Line (I hope). color isn't changing, but it can be implemented. Let’s see it in action:
import kivy
from kivy.app import App
from kivy.graphics import *
from kivy.core.window import Window
from kivy.uix.floatlayout import FloatLayout
from guy.who.helped import ourLine
root=FloatLayout()
#tell us your widget so we can refer
class myLine(ourLine):
widget=root
#you may want to start listening later, so it is manual.
ourLine.activate(myLine)
#you can start using like a normal Line
with root.canvas:
Color(1,1,0,1)
myLine(bezier=[2,70,90,80,Window.width,Window.height])
myLine(bezier=[200,170,309,80,Window.width/2,Window.height*0.8], width=12)
class MyApp(App):
def build(self):
return root
if __name__ == '__main__':
MyApp().run()
With what, is this post better than others? Well, this can use Bézier curves, not points and is responsive.
And weirdly, it is not so bad with performance.
It fires when you resize the window. So, the rest of the time, as good as basic Line + a few bytes of RAM to hold relative values and extras.
Well, I have bad news. Once I wanted to animate Bézier curves, I could animate the Bezier class beziers, but not Line(bezier=(...)). and width of Bezier is can't be changed, because there is no such property. And I ended up with animating only 1px width Bézier curves. So, there is not much with dynamic vector lines in Kivy... yet (I hope).
I love the simplicity of that library, but it is not mature yet I guess. And I decided to move on.
There are a lot of things that doesn't have Kivy's problems, such as web and webframeworks (which I chose over Kivy). I love Python, and Kivy is so capable. but when it comes a little bit down to specific things, Kivy really lacks :'(

How to refer to pos and size of widget in python canvas (KIVY)?

Here is what I do now, I create a function that can dynamically create a button (in boxlayout):
def create_class_button(self, size_hint_x:float, text:str, text_color:tuple, background_color:tuple) -> TextBoxLayout:
# Ignore this box
box = TextBoxLayout(size_hint_x=size_hint_x, padding=(30,6.5), on_press=self.list_item_pressed, background_color=(0,0,0,0))
# RoundedButton inherited from Button
btn = RoundedButton(text=text, on_press=self.list_item_pressed, color=text_color, background_color = (0,0,0,0))
with btn.canvas.before:
Color(rgba=background_color)
Rectangle(size=btn.size, pos=btn.pos)
box.add_widget(btn)
return box
The canvas always draw at the point (0, 0).
How can I let it follow my RoundedButton position and size?
After reading your question several time, I think you are asking how to get the Rectangle to follow your RoundedButton. The best way to do that is to create a rule in kv for the RoundedButton:
<RoundedButton>:
canvas.before:
Color:
rgba: self.background_color
Rectangle:
pos: self.pos
size: self.size
You can do the same in python, but then you must set up bindings to get the Rectangle to follow the RoundedButton. The kv language sets up those bindings for you.
Also, note that the pos and size of a Widget initially get set to the defaults of (0,0) and (100,100), and they get updated when the Widget is drawn. So your canvas is using those initial defaults. And since no bindings are set up, the canvas does not change when the pos and size of the Widget change.

Kivy Label add text without overlapping

I am making a simple chat room application, and it is working perfectly other than the fact that when a user enters a long message, it overlaps the other messages in the ScrollView like this:
This is the code for the label:
message_lab = Label(text=text, markup=True, size_hint_y=None)
message_lab.width = self.width
message_lab.text_size = message_lab.width, None
self.chat.add_widget(message_lab)
self.scroller.scroll_to(message_lab)
Note(s):
The value of self.width is 800
self.chat is a BoxLayout which is the layout inside the ScrollView
self.scroller is the ScrollView
Any help I receive is greatly appreciated!
Use a kv rule and Label subclass, something like:
<YourLabel>:
height: self.texture_size[1]
text_size: self.width, None

Kivy places widgets slightly differently through Python and Kivy language. Am I missing something?

I just picked up Kivy and encountered this problem. If there is a better way to achieve what I'm trying to in general I'd love to hear about it, though.
What I've noticed is that, when I add a widget to another widget, if I do so through Python code it will be slightly at a different position than had I done so through Kivy. I'll paste my code below (it's pretty short right now) and you can just try it yourself and you'll see what I mean.
client.py:
import kivy
kivy.require('1.9.1') # current kivy version
from kivy.config import Config
Config.set('graphics', 'width', '360')
Config.set('graphics', 'height', '640')
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import ObjectProperty, NumericProperty, ReferenceListProperty
from kivy.graphics import Color, Rectangle
from kivy.clock import Clock
from kivy.vector import Vector
from random import randint
class Bird(Widget):
'''
Bird Widget defines each sprite. / Test version: blue and red cards
Defined attributes:
SIZE
POSITION
(COLOR)
Upade the position of this widget individually every 0.7 seconds with 60 fps
'''
# set attributes
border_color = (1,1,1)
r = NumericProperty(0)
g = NumericProperty(0)
b = NumericProperty(0)
color = ReferenceListProperty(r, g, b) # initial color = red // maybe make it random
velocity_x = NumericProperty(0)
velocity_y = NumericProperty(-3)
velocity = ReferenceListProperty(velocity_x, velocity_y)
def __init__(self, **kwargs):
super(Bird, self).__init__(**kwargs)
self.pick_color()
# Randomly generate 0 or 1, and pick a color based on that
def pick_color(self):
color_num = randint(0,1)
if color_num == 0: # blue
self.color = (0,0,1)
elif color_num == 1: # red
self.color = (1,0,0)
# Move the widget by -3y increment at 60 fps
def increment(self, dt):
self.pos = Vector(*self.velocity) + self.pos
def move(self):
# While the sprite moves at 60 fps, the movement is "cancelled" after 0.3 seconds
# This event sequence is refreshed every 0.7 seoncds in MainApp class
move = Clock.schedule_interval(self.increment, 1.0/60.0)
stop = Clock.schedule_once(lambda dt: move.cancel(), 0.3)
class GameMain(Widget):
'''
Contains two functions: ADD_NEW_BIRD() and UPDATE().
All controls happen in this widget
Not using kivy.screen because there is only one screen used
ADD_NEW_BIRD() adds a new bird to list_of_birds AND add it as a child widget to the GameMain Widget. UPDATE() calls MOVE() (Bird Widget) and receives events
Create global variable limit = 0; if limit == 4, game over; variable accessed in update function, which checks whether the limit has been reached. If the player makes the right decision, then limit -= 1
'''
limit = 0
def add_new_bird(self):
self.new_bird = Bird(center_x=self.center_x, center_y=self.height/1.5)
print (self.center_x, self.height)
self.new_bird.pick_color()
self.add_widget(self.new_bird)
def update(self, dt):
for bird in self.children:
bird.move()
self.add_new_bird()
class MainApp(App):
def build(self):
game = GameMain()
Clock.schedule_interval(game.update, 0.7)
return game
if __name__ == '__main__':
MainApp().run()
main.kv:
#:kivy 1.9
<Bird>:
size: 70, 80
canvas:
Color:
rgb: self.border_color
Rectangle:
size: self.size
pos: self.pos
Color:
rgb: self.color
Rectangle:
size: root.width - 10, root.height - 10
pos: root.x + 5, root.y + 5
<GameMain>
Bird:
center_x: root.center_x
center_y: root.height / 1.5
The code does exactly what I want it to do (I'm going to touch on the z-values later) except that the very first card is slightly off to the left. I'm just really confused because center_x: root.center_x in main.kv should not be any different from Bird(center_x=self.center_x in client.py as far as I understand. I've tried initializing the first instance of Bird() inside of an init function like so:
def __init__(self, **kwargs):
super(GameMain, self).__init__(**kwargs)
self.bird = Bird(center_x=self.center_x, center_y=self.height/1.5)
self.bird.pick_color()
self.add_widget(self.bird)
And the problem was still there! If anyone could explain what's going on/what I'm doing wrong and maybe even suggest a better way to approach this, I'd appreciate it.
Just in case you're curious, I need to add widgets directly from Python code because I need the app to constantly produce a new card at a constant time interval. The first card, however, is initialized in the Kivy file for the sake of simplicity. To be fair it works pretty well except for the offset. And lastly I'm not using a Layout because I wasn't sure which one to use... I did lay my hands on FloatLayout for a bit but it didn't seem like it was going to fix my problem anyway.
When constructed, Widget has an initial size of (100, 100). If you change size from this:
<Bird>:
size: 70, 80
to this:
<Bird>:
size: 100, 80
rectangles will align correctly. Initial rectangle, created in kv file, is centered at the parent window, other ones that are created in Python code are offset to the left.
If you change Bird constructor in Python code from this:
def __init__(self, **kwargs):
super(Bird, self).__init__(**kwargs)
self.pick_color()
to this (effectively overriding the default widget size from (100, 100) to be (50,50)):
def __init__(self, **kwargs):
self.size = (50, 50)
super(Bird, self).__init__(**kwargs)
self.pick_color()
you'll notice that rectangles created in Python code will shift to the right. Change kv file from:
<Bird>:
size: 70, 80
to:
<Bird>:
size: 50, 80
which matches (new) initial widget size of (50,50) in width, all rectangles will be aligned again.
Solution to your problem would be to leave all as is, except to set size for new birds in Python to be equal to that in kv file:
def __init__(self, **kwargs):
self.size = (70, 80)
super(Bird, self).__init__(**kwargs)
self.pick_color()
and all will work as intended.
This all means that size property from kv file is not applied to your Python-side created Birds, only to the one created by kv declaration. Is this Kivy bug or maybe you are missing one more step in the Python code to make Builder apply size from kv file to Python-created Birds, I have no idea right now.
In my experience, at this point of Kivy development, mixing too much kv and Python code will result in these kind of weird issues you have here. It is best to either handle all view related stuff in kv or to ditch kv completely and build everything in Python.
Some things don't work at all in kv, i.e. setting cols property of GridLayout (v1.9.1).
Personally, for now, I stick to well organized Python code to build UI and don't use kv files almost at all.
Hope this helps a bit...

Kivy layout positioning problems - not understanding/properly using __init__, kv, kv properties

I am a new programmer (and first time stackoverflow poster) so please correct me if I use terminology incorrectly or make any other missteps in etiquette or proper coding style.
I am trying to write a game where you draw tiles to your tile rack and then play them on a board. I have already written a game that works without graphics via text input. Now I would like to use Kivy to make a graphical interface.
One of my problems involves the positioning of widgets. I would like to center my rack widget at the center of the screen x-axis. I can have it draw a rectangle there and have it appear to be positioned where I want it, but its x position is (as you might guess) 0. I think part of my problem is that I have passed a Game object to my widget and using a list of symbols (game.symbols) and an init method, I tried to load create tile widgets with a label(text=symbol) and then load them on the rack. As you probably have guessed, my tiles also are not positioned correctly.
How can I center my tile rack and load my tiles correctly so they have the proper position (which I think is necessary for my collision detection).
Please explain the way init method and the KV file are executed when both are used.
What is the proper way to pass objects and attributes to widgets in regards to my issues here. Should I have created an ObjectProperty?
I also may just have a fundamental misunderstanding of positioning and layouts in Kivy and if so, please educate me.
Thank you,
Cliff
import kivy
kivy.require('1.7.0')
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.scatter import Scatter
from kivy.uix.label import Label
kv_Game= '''
<TileWidget>:
size_hint: None, None
size: 50,50
canvas.before:
Color:
rgba: 0.5,0.5,1,0.3
Rectangle:
size: self.width, self.height
canvas.after:
Line:
rectangle: self.x, self.y, self.width, self.height
dash_offset: 5
dash_length: 3
<RackWidget>:
size_hint: None, None
size: 350, 50
pos_hint: {'center_x': 0.5}
y: 75
canvas.after:
Color:
rgba: 1,0,0,0.5
Line:
rectangle: self.x, self.y, self.width, self.height
'''
Builder.load_string(kv_Game)
class Game(FloatLayout):
def __init__(self, **kwargs):
super(Game, self).__init__(**kwargs)
self.symbols = ['!','#','#','$','%','^','&']
self.rackWidget = RackWidget(self)
self.add_widget(self.rackWidget)
class TileWidget(Scatter):
def __init__(self, symbol="?", **kwargs):
super(TileWidget, self).__init__(**kwargs)
tileLabel = Label(text=symbol, size_hint=(None,None), size=(50,50))
self.add_widget(tileLabel)
class RackWidget(FloatLayout):
def __init__(self, game, **kwargs):
super(RackWidget, self).__init__(**kwargs)
print("TileRackWidget pos:", self.pos)
x, y = self.pos
for symbol in game.symbols:
tileWidget = TileWidget(symbol=symbol, pos= (x,y))
self.add_widget(tileWidget)
print("tileWidget pos:", tileWidget.pos)
x+=50
class GameTest1App(App):
def build(self):
game = Game()
return game
if __name__=="__main__":
GameTest1App().run()
pos is not set to a usable value yet when you create your RackWidget instance. When __init__ is running, the widget has not yet been added to the Game widget, so it has no parent and no positioning information. You could solve this by binding to the changes in RackWidget.pos, but there's an easier way to do this: RelativeLayout. The position of each child of a RelativeLayout will be based on the position of the layout itself.
Here's a modified version of RackWidget using RelativeLayout:
class RackWidget(RelativeLayout):
def __init__(self, game, **kwargs):
super(RackWidget, self).__init__(**kwargs)
print("TileRackWidget pos:", self.pos)
x, y = 0, 0
for symbol in game.symbols:
tileWidget = TileWidget(symbol=symbol, pos= (x,y))
self.add_widget(tileWidget)
print("tileWidget pos:", tileWidget.pos)
x+=50

Categories

Resources