I am looking to replace the windows title bar for a borderless app, I found some solutions on the internet that didn't quite work for me so I tried to do it myself.
Although the grabbing the screen and moving part works, once you release the click, the window continues to follow the cursor until eventually the program stops responding and the task is terminated.
This is an example of code that I prepared with some indications on how it works:
from kivy.app import App
from win32api import GetSystemMetrics
from kivy.lang.builder import Builder
from kivy.core.window import Window
from kivy.uix.widget import Widget
import pyautogui
import win32api
import re
Window.size=(600,300)
Window.borderless=True
#The following causes the window to open in the middle of the screen:
Window.top=((GetSystemMetrics(1)/2)-150)
Window.left=((GetSystemMetrics(0)/2)-300)
#####################################################################
Builder.load_string("""
<Grab>
GridLayout:
size:root.width,root.height
cols:2
Label:
id:label
text:'A label'
Button:
id:button
text:'The button that changes the window position'
on_press: root.grab_window()
""")
class Grab(Widget):
def grab_window(self):
#The following saves the mouse position relative to the window:
Static_Mouse_pos=re.findall('\d+',str(pyautogui.position()))
Mouse_y=int(Static_Mouse_pos[1])-Window.top
Mouse_x=int(Static_Mouse_pos[0])-Window.left
###############################################################
#The following is what causes the window to follow the mouse position:
while win32api.GetKeyState(0x01)<0: #In theory this should cause the loop to start as soon as it is clicked, I ruled out that it would start and end when the button was pressed and stopped being pressed because as soon as the screen starts to move, it stops being pressed.
Relative_Mouse_pos=re.findall('\d+',str(pyautogui.position()))
Window.left=(int(Relative_Mouse_pos[0])-Mouse_x)
Window.top=(int(Relative_Mouse_pos[1])-Mouse_y)
print(f'Mouse position: ({Mouse_x},{Mouse_y})') #To let you know the mouse position (Not necessary)
print(f'Window position: ({Window.top},{Window.left})') #To let you know the position of the window (Not necessary)
if win32api.GetKeyState(0x01)==0: #This is supposed to stop everything (Detects when you stop holding the click)
break
######################################################################
class app(App):
def build(self):
return Grab()
if __name__=='__main__':
app().run()
Is there a way to make it work fine? Or another way to grab a borderless window that might be effective?
I'm new to programming, so I apologize in advance for any nonsense you may read in my code.
EDIT: For some reason win32api.GetKeyState(0x01) is not updated once the click is done and the loop is started, nor does it help to make a variable take its value.
I've finally come up with a solution but this may not be the best one.
( Some places where I made changes are marked with comment [Modified] )
from kivy.app import App
from win32api import GetSystemMetrics # for getting screen size
from kivy.lang.builder import Builder
from kivy.core.window import Window
from kivy.uix.widget import Widget
import pyautogui
# import win32api
# import re
# set window size
# Window.size=(600,300)
# make the window borderless
Window.borderless = True
# The following causes the window to open in the middle of the screen :
Window.left = ((GetSystemMetrics(0) / 2) - Window.size[0] / 2) # [Modified] for better flexibility
Window.top = ((GetSystemMetrics(1) / 2) - Window.size[1] / 2) # [Modified] for better flexibility
#####################################################################
Builder.load_string("""
<Grab>
GridLayout:
size: root.width, root.height
rows: 2 # [modified]
Button:
id: button
text: "The button that changes the window position"
size_hint_y: 0.2
Label:
id: label
text: "A label"
""")
class Grab(Widget):
# I'm sorry I just abandoned this lol
"""
def grab_window(self):
#The following saves the mouse position relative to the window:
Static_Mouse_pos=re.findall('\d+',str(pyautogui.position()))
Mouse_y=int(Static_Mouse_pos[1])-Window.top
Mouse_x=int(Static_Mouse_pos[0])-Window.left
###############################################################
#The following is what causes the window to follow the mouse position:
while win32api.GetKeyState(0x01)<0: #In theory this should cause the loop to start as soon as it is clicked, I ruled out that it would start and end when the button was pressed and stopped being pressed because as soon as the screen starts to move, it stops being pressed.
Relative_Mouse_pos=re.findall('\d+',str(pyautogui.position()))
Window.left=(int(Relative_Mouse_pos[0])-Mouse_x)
Window.top=(int(Relative_Mouse_pos[1])-Mouse_y)
print(f'Mouse position: ({Mouse_x},{Mouse_y})') #To let you know the mouse position (Not necessary)
print(f'Window position: ({Window.top},{Window.left})') #To let you know the position of the window (Not necessary)
if win32api.GetKeyState(0x01)==0: #This is supposed to stop everything (Detects when you stop holding the click)
break
######################################################################
"""
def on_touch_move(self, touch):
if self.ids.button.state == "down": # down | normal
# button is pressed
# mouse pos relative to screen , list of int
# top left (0, 0) ; bottom right (max,X, maxY)
mouse_pos = [pyautogui.position()[0], pyautogui.position()[1]]
# mouse pos relative to the window
# ( normal rectangular coordinate sys. )
mouse_x = touch.pos[0]
mouse_y = Window.size[1] - touch.pos[1] # since the coordinate sys. are different , just to converse it into the same
# give up using touch.dx and touch.dy , too lag lol
Window.left = mouse_pos[0] - mouse_x
Window.top = mouse_pos[1] - mouse_y
class MyApp(App): # [Modified] good practice using capital letter for class name
def build(self):
return Grab()
if __name__ == "__main__":
MyApp().run()
I just gave up using button on_press or on_touch_down as suggested in the comment since it requires manual update for the mouse position.
Instead , I try using the Kivy built-in function ( ? ) on_touch_move.
It is fired when a mouse motion is detected inside the windows by the application itself. ( much more convenient compared with manual checking lol )
The concepts of window positioning are similar to yours , which is mouse pos relative to screen - mouse pos relative to app window. But the coordinate system used by Pyautogui and Kivy 's window are different , therefore I did some conversion for this as seen in the code above.
But I'm not sure whether the unit used by Pyautogui and Kivy for mouse positioning is the same or not ( ? ), so it would not be as smooth as expected / ideal case when drag-and-dropping the window via the button. Also the time delay for updating when on_touch_move of the kivy app. That's the reason why I think it may be no the best answer for your question.
Any other solutions / suggestions / improvements etc. are welcome : )
Simplified Code For Copy-Paste :
Edit : Added close / minimize window button ( at top-left corner )
#
# Windows Application
# Borderless window with button press to move window
#
import kivy
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.widget import Widget
from kivy.clock import Clock
from win32api import GetSystemMetrics # for getting screen size
import pyautogui # for getting mouse pos
# set window size
# Window.size = (600,300)
# make the window borderless
Window.borderless = True
# set init window pos : center
# screen size / 2 - app window size / 2
Window.left = (GetSystemMetrics(0) / 2) - (Window.size[0] / 2)
Window.top = (GetSystemMetrics(1) / 2) - (Window.size[1] / 2)
kivy.lang.builder.Builder.load_string("""
<GrabScreen>
Button:
id: close_window_button
text: "[b] X [b]"
font_size: 25
markup: True
background_color: 1, 1, 1, 0
size: self.texture_size[0] + 10, self.texture_size[1] + 10
border: 25, 25, 25, 25
on_release:
root.close_window()
Button:
id: minimize_window_button
text: "[b] - [b]"
font_size: 25
markup: True
background_color: 1, 1, 1, 0
size: self.texture_size[0] + 10, self.texture_size[1] + 10
border: 25, 25, 25, 25
on_release:
root.minimize_window()
Button:
id: move_window_button
text: "[b]. . .[/b]"
font_size: 25
markup: True
background_color: 1, 1, 1, 0
width: root.width / 3
height: self.texture_size[1] * 1.5
border: 25, 25, 25, 25
Label:
id: this_label
text: "Hello World !"
font_size: 25
size: self.texture_size
""")
class GrabScreen(Widget):
def close_window(self):
App.get_running_app().stop()
# def on_window_minimize(self, *args, **kwargs):
# print(args)
def minimize_window(self):
Window.minimize()
# Window.bind(on_minimize = self.on_window_minimize)
def maximize_window(self):
Window.size = [GetSystemMetrics(0), GetSystemMetrics(1)]
Window.left = 0
Window.top = 0
def update(self, dt):
# button for closing window
self.ids.close_window_button.top = self.top
# button for minimizing window
self.ids.minimize_window_button.x = self.ids.close_window_button.right
self.ids.minimize_window_button.top = self.top
# button for moving window
self.ids.move_window_button.center_x = self.center_x
self.ids.move_window_button.top = self.top
# label
self.ids.this_label.center = self.center
def on_touch_move(self, touch):
# when touching app screen and moving
if self.ids.move_window_button.state == "down": # down | normal
# (button move_window_button is pressed) and (mouse is moving)
# mouse pos relative to screen , list of int
# top left (0, 0) ; bottom right (maxX, maxY)
mouse_pos = [pyautogui.position()[0], pyautogui.position()[1]] # pixel / inch
# mouse pos relative to the window
# ( normal rectangular coordinate sys. )
# since the coordinate sys. are different , just to converse it to fit that of pyautogui
# Note :
# 1 dpi = 0.393701 pixel/cm
# 1 inch = 2.54 cm
"""
n dot/inch = n * 0.393701 pixel/cm
1 pixel/cm = 2.54 pixel/inch
n dot/inch = n * 0.393701 * 2.54 pixel/inch
"""
mouse_x = touch.x # dpi
mouse_y = self.height - touch.y # dpi
# update app window pos
Window.left = mouse_pos[0] - mouse_x
Window.top = mouse_pos[1] - mouse_y
# max / min window
if mouse_pos[1] <= 1:
self.maximize_window()
elif Window.size[0] >= GetSystemMetrics(0) and Window.size[1] >= GetSystemMetrics(1):
Window.size = [Window.size[0] / 2, Window.size[1] * 0.7]
class MyApp(App):
grabScreen = GrabScreen()
def build(self):
# schedule update
Clock.schedule_interval(self.grabScreen.update, 0.1)
return self.grabScreen
if __name__ == "__main__":
MyApp().run()
Reference
Kivy Motion Event
Related
First, I am a beginner with kivy, and not very experienced in Python either.
The following program is strictly meant to educate myself in kivy basics, nothing more.
The program is a game where the user controls a gun turret. He shots at objects that bounce around in the program window, while points and time used are shown in the bar below. (Currently occupied by a button.) The keys that turn the turret are "A" and "D", while "P" is for shooting.
The problem I am currently having is this:
I need to generate additional projectile graphics every time the user press "P". At a later stage I will also need to generate and remove target objects graphics when they get hit by a shot, which I guess will involve the same technique.
How should I go about to generate additional canvasĀ“ and\or ellipse objects? (the graphical representation of each shot)
My Python code:
from kivy.config import Config
Config.set('graphics', 'width', '800')
Config.set('graphics', 'height', '900')
Config.set('graphics','resizable',0)
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.widget import Widget
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.relativelayout import RelativeLayout
from kivy.properties import NumericProperty
from kivy.properties import ReferenceListProperty
from kivy.properties import BooleanProperty
from kivy.properties import ListProperty
from kivy.clock import Clock
import math
from kivy.core.window import Window
class MainScreen(BoxLayout): #Contains the whole program
#Turret rotation:
the_angle = NumericProperty(0)
#Projectile vector:
the_pos_x = NumericProperty(0)
the_pos_y = NumericProperty(0)
the_pos = ReferenceListProperty(the_pos_x, the_pos_y)
#Shots data:
shot_fired = BooleanProperty(False)
shot_count = NumericProperty(0)
angle_list = ListProperty([])
pos_list_x = ListProperty([])
pos_list_y = ListProperty([])
def update(self, dt):
if self.shot_fired == True:
self.shot_count += 1
self.angle_list.append(self.the_angle)
self.pos_list_x.append(400)
self.pos_list_y.append(400)
self.shot_fired = False
for i in range(0, self.shot_count):
self.shoot(i, self.angle_list[i], self.pos_list_x[i], self.pos_list_y[i])
def shoot(self, cur_shot, the_angle, pos_x, pos_y):
#Make correct shot data:
velocity = 1.5
angle_rad = math.radians(the_angle+90)
pos_x += ( math.cos(angle_rad)*velocity )
pos_y += ( math.sin(angle_rad)*velocity )
#Give the shots positions new data :
self.the_pos_x = pos_x
self.the_pos_y = pos_y
#Update data for the next round of this function:
self.pos_list_x[cur_shot] = pos_x
self.pos_list_y[cur_shot] = pos_y
def keypress(self, *args):
the_key = args[3]
if the_key == "a":
self.the_angle += 1.5
elif the_key == "d":
self.the_angle += -1.5
elif the_key == "p":
self.shot_fired = True
class GameScreen(RelativeLayout): #The game part of the screen
pass
class Gun(Widget): #Holds canvas and drawings for gun
pass
class Projectile(Widget): #Holds canvas and drawings for projectile
pass
class InfoBar(FloatLayout): #Holds point counters and buttons
pass
#Set screen size and prevent resizing:
Window.size = (800, 900)
class MyApp(App):
def build(self):
Builder.load_file("interface.kv")
game = MainScreen()
Window.bind(on_key_down=game.keypress)
Clock.schedule_interval(game.update, 1.0/60.0)
return game
MyApp().run()
My kv code:
<MainScreen>:
the_angle: 45
the_pos: [400,400]
GameScreen:
size_hint:(None,None)
size:800,800
pos_hint:{"top":1, "left":1}
Gun:
canvas.before:
PushMatrix
Rotate:
axis: 0,0,1
angle: root.the_angle
origin: 400,400
canvas:
Rectangle:
size:50,100
pos: 400,400
canvas.after:
PopMatrix
Projectile:
canvas:
Ellipse:
size:10,20
pos: root.the_pos[0], root.the_pos[1]
InfoBar:
size_hint:(None,None)
size:800,100
pos_hint:{"bottom":1, "left":1}
Button:
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...
I have a class that changes the background color constantly with a Clock.schedule_interval in init. I would like to create multiple instances of this class simultaneous; however, I think this means creating multiple threads which isn't allowed? What I would like is the top half to be changing colors while the bottom half is changing colors differently. What is happening is only the bottom half is changing colors while the top half is black. So here is the code.
The /teacher/main.py file is
from kivy.app import App
from kivy.clock import Clock
from kivy.graphics import Color
from kivy.properties import NumericProperty, ReferenceListProperty
from kivy.uix.gridlayout import GridLayout
from kivy.uix.widget import Widget
from random import randint
class ChangingBackgroundColor(Widget):
r = NumericProperty(.5)
g = NumericProperty(.5)
b = NumericProperty(.5)
a = NumericProperty(1)
color = ReferenceListProperty(r, g, b, a)
def __init__(self,**kwargs):
super(ChangingBackgroundColor, self).__init__(**kwargs)
Clock.schedule_interval(self.update, .2)
def update(self, dt):
position = randint(0,2) # change to randint(0,3) to change a as well
direction = randint(0,1)
if direction == 0:
if self.color[position] == 0:
self.color[position] += .1
else:
self.color[position] -= .1
elif direction == 1:
if self.color[position] == 1:
self.color[position] -= .1
else:
self.color[position] += .1
self.color[position] = round(self.color[position], 2)
self.canvas.add(Color(self.color))
class TeachingApp(App):
def build(self):
grid = GridLayout(rows=2)
a = ChangingBackgroundColor()
b = ChangingBackgroundColor()
grid.add_widget(a)
grid.add_widget(b)
return grid
if __name__ == '__main__':
TeachingApp().run()
and the /teacher/teaching.kv file is
#:kivy 1.0.9
<ChangingBackgroundColor>:
canvas:
Color:
rgba: self.color
Rectangle:
size: self.width, self.height
I looked here and and still fuzzy on the threading issue. Clock documentation.
This is my first question I have submitted so if I did anything wrong regarding question submission please let me know.
Your code is fine, using Clock.schedule_interval doesn't use threads (it's all in the main thread), and can be used from other threads even if you did have them, although callbacks would still happen in the main thread.
The problem is that your Rectangle entry in the kv needs to have:
pos: self.pos
Without this, both rectangles have the default pos of (0, 0), so the second one is on top of the first one and the top half of the screen is black.
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
I am trying to create some sort of interactive "wall" with raspberry pi and the kivy library.
I would like to display images (maybe also text later) vertically one below the other and move them up/down on a keypress. Kind of scroll behavior but I would like to display many images so I do not want to load all of them at one go and use a scroll view.
So I use a FloatLayout and I change the position of the images after a keypress. My first issue is, that I have to operate with Window.width / Window.height instead of size_hint / pos_hint. I tried to use the examples from the kivy FloatLayout documentation but it did not work.
My problem is, that after hitting the keys the images are moving, but they also change the size. Here is the code I am using.
EDIT:
I realized that adding size_hint=(None, None) will keep the image size. This only shows that it would be probably better to use the relative positioning and sizing of the images in the layout. As mentioned earlier the example from the FloatLayout documentation did not work for me.
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.image import Image
from kivy.core.window import Window
class Wall(FloatLayout):
def __init__(self, **kwargs):
super(FloatLayout, self).__init__(**kwargs)
self._keyboard = Window.request_keyboard(self._keyboard_closed, self)
if not self._keyboard:
return
self._keyboard.bind(on_key_down=self.on_keyboard_down)
self.posts = list()
self.orientation = 'vertical'
spacing = 25
post = Image(source='test.png',
allow_stretch=True,
keep_ratio=True,
size=(Window.width * 0.2z, Window.height * 0.2),
center=(Window.width * 0.6, Window.height * 0.5))
print '#####\nOriginal size and position: width/height', post.width, post.height, ' position x/y', post.x, post.y
self.posts.append(post)
self.add_widget(post)
while self.posts[-1].y + self.posts[-1].height <= Window.height:
post = Image(source='test.jpg',
allow_stretch=True,
keep_ratio=True,
size=(Window.width * 0.2, Window.height * 0.2),
pos=(self.posts[-1].x, self.posts[-1].y + self.posts[-1].height + spacing))
print '#####\nOriginal size and position: width/height', post.width, post.height, ' position x/y', post.x, post.y
self.posts.append(post)
self.add_widget(post)
def _keyboard_closed(self):
self._keyboard.unbind(on_key_down=self._on_keyboard_down)
self._keyboard = None
def on_keyboard_down(self, keyboard, keycode, text, modifiers):
key = keycode[1]
for post in self.children:
if key == 'up':
post.y -= 50
print '#####\nNew size and position: width/height', post.width, post.height, ' position x/y', post.x, post.y
elif keycode[1] == 'down':
post.y += 50
elif key == 'q':
App.get_running_app().stop()
#else:
# return False
#return True
return True
class MessageBoard(App):
def build(self):
return Wall()
if __name__ == '__main__':
MessageBoard().run()
I found a solution using RelativeLayout and working with the pos_hint / size_hint properties. The positive side efect is, that the elements keep their size/position even when the window resizes.
I still need to compute absolute coordinates to determine the position of the elements. The drawback is, that in the init() method, the size of the parrent element is not knownw, so I have to work with Window size and coorinates. This will fail if my wall would not be the root element.
The moving of the elements need to be done by setting of the pos_hint property, changing the absolute coordinates would not take any effects.
class ImageAndText(RelativeLayout):
pass
class Wall(FloatLayout):
def __init__(self, **kwargs):
super(Wall, self).__init__(**kwargs)
self._keyboard = Window.request_keyboard(self._keyboard_closed, self)
if not self._keyboard:
return
self._keyboard.bind(on_key_down=self.on_keyboard_down)
self.posts = list()
post = ImageAndText()
post.size_hint = 1, 0.3
post.size = (Window.width, Window.height * 0.3) # needs to be set in order to determine the absolute size
post.center_y = Window.height * 0.5 # needs to be set in order to determine the absolute coordinates
post.pos_hint = {'center_y': 0.5}
self.add_widget(post)
self.posts.append(post)
while self.posts[-1].top <= Window.height:
#print 'Old post top:', self.posts[-1].top
post = ImageAndText()
post.size_hint = 1, 0.25
post.size = (Window.width, Window.height * 0.25)
post.y = self.posts[-1].top # just above the first one
post.pos_hint = {'center_y': post.center_y/Window.height}
self.add_widget(post)
self.posts.append(post)
def _keyboard_closed(self):
self._keyboard.unbind(on_key_down=self._on_keyboard_down)
self._keyboard = None
def on_keyboard_down(self, keyboard, keycode, text, modifiers):
key = keycode[1]
for post in self.posts:
if key == 'up':
post.pos_hint = {'center_y': post.pos_hint['center_y']+0.1}
elif keycode[1] == 'down':
post.pos_hint = {'center_y': post.pos_hint['center_y']-0.1}
elif key == 'q':
App.get_running_app().stop()
return True
class MyTestApp(App):
def build(self):
return Wall()
if __name__ == "__main__":
MyTestApp().run()
Here is the .kv file describing the ImageAndText element.
<ImageAndText>:
Label:
font_size: '14sp'
text: 'Lorem ipsum dolor sit'
color: (1, 1, 1, 1)
text_size: (self.parent.width*0.25, None)
pos_hint: {'center_x': 1.0/6} # set the center to 1/6 of parents width
Image:
source: 'test.png'
pos_hint: {'center_x': 2.0/3, 'center_y': .5}
size_hint: 0.5, 0.95 # the height will be the same as the parent, the width 0.5 because of the label
allow_stretch: True