I am developing a game in which users must match images by their initial letter (in Spanish), so that when they drag to a point (the cauldron) an image that begins with the correct letter (in this case the igloo, the Indian and the magnet) this image disappears.Example screen
In other words, basically, an image disappears when dragged to a specific point.
*.kv
#:import win kivy.core.window
<Picture#Scatter>:
source: None
on_size: self.center = win.Window.center
size: image.size
size_hint: None, None
do_scale: False
do_rotation: False
Image:
id: image
source: root.source
size: 250, 250 / self.image_ratio
<Relaciona3x2>:
AnchorLayout:
Image:
source: 'data/img/fondobosque.jpg'
allow_stretch: True
keep_ratio: False
FloatLayout:
size_hint: 1, 1
Image:
id: img003
source: 'data/img/caldero.png'
size_hint: 0.55, 0.55
pos_hint: {"center_x": 0.5, "center_y": 0.20}
Button:
size_hint:.06, 0.1
text: "Volver al menú"
on_release: app.root.current = 'menu'
Picture:
id: potionscatter
source: "data/img/letra_i/iglú.png"
pos: 175, 680
Picture:
source: "data/img/letra_i/indio.png"
pos: 835, 680
Picture:
source: "data/img/letra_m/moto.png"
pos: 1495, 680
Picture:
source: "data/img/letra_u/uña.png"
pos: 175, 420
Picture:
source: "data/img/letra_i/imán_1.png"
pos: 835, 420
Picture:
source: "data/img/letra_u/urraca.png"
pos: 1495, 420
<Relaciona4x2Screen>:
AnchorLayout:
Image:
source: 'data/img/fondobosque.jpg'
allow_stretch: True
keep_ratio: False
FloatLayout:
size_hint: 1, 1
Image:
id: img003
source: 'data/img/caldero.png'
size_hint: 0.55, 0.55
pos_hint: {"center_x": 0.5, "center_y": 0.20}
Button:
size_hint:.06, 0.1
text: "Volver al menú"
on_release: app.root.current = 'menu'
<Relaciona5x2Screen>:
AnchorLayout:
Image:
source: 'data/img/fondobosque.jpg'
allow_stretch: True
keep_ratio: False
FloatLayout:
size_hint: 1, 1
Image:
id: img003
source: 'data/img/caldero.png'
size_hint: 0.55, 0.55
pos_hint: {"center_x": 0.5, "center_y": 0.20}
Button:
size_hint:.06, 0.1
text: "Volver al menú"
on_release: app.root.current = 'menu'
relaciona.py
__all__ = ('Relaciona3x2', 'Relaciona4x2', 'Relaciona5x2')
import kivy
kivy.require('1.0.6')
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
Builder.load_file('relaciona.kv')
class Relaciona3x2(Screen):
pass
class Relaciona4x2(Screen):
pass
class Relaciona5x2(Screen):
pass
main.py
import kivy
kivy.require('1.0.6')
from kivy.uix.popup import Popup
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.animation import Animation
from kivy.uix.button import Button
from kivy.uix.scatter import Scatter
from kivy.logger import Logger
from kivy.uix.gesturesurface import GestureSurface
from letras import DrawGame, ImageEx, SoundEx, ContainerBox
from explota import BubblePop, BubbleGame
from relaciona import *
from borra import Borra
from caza import Caza
from config import Config
from kivy.core.window import Window
Builder.load_file('design.kv')
class MyScreenManager(ScreenManager):
def __init__(self):
super (MyScreenManager, self).__init__()
class HomeMenu(Screen):
pass
class LetrasScreen(Screen):
pass
class ExplotaScreen(Screen):
pass
class Relaciona3x2Screen(Screen):
pass
class Relaciona4x2Screen(Screen):
pass
class Relaciona5x2Screen(Screen):
pass
class BorraScreen(Screen):
pass
class CazaScreen(Screen):
pass
class ConfigScreen(Screen):
pass
class myApp(App):
def build(self):
Window.fullscreen = 'auto'
return MyScreenManager()
def on_pause(self):
return True
def on_resume(self):
pass
myApp().run()
I have used DragNDropWidget to solve this problem. It's quite simple to use but now I don't know how to change the size of the buttons, I would like them to be bigger and somewhat separated from each other.
DragNDropWidget.py
#!/usr/bin/python
# -*- coding: UTF-8 -*-
from kivy.core.window import Window
from kivy.animation import Animation
import copy
from kivy.uix.widget import Widget
from kivy.properties import (
ListProperty, NumericProperty, BooleanProperty, ObjectProperty)
class DragNDropWidget(Widget):
# let kivy take care of kwargs and get signals for free by using
# properties
droppable_zone_objects = ListProperty([])
bound_zone_objects = ListProperty([])
drag_opacity = NumericProperty(1.0)
drop_func = ObjectProperty(None)
drop_args = ListProperty([])
remove_on_drag = BooleanProperty(True)
def __init__(self, **kw):
super(DragNDropWidget, self).__init__(**kw)
self.register_event_type("on_drag_start")
self.register_event_type("on_being_dragged")
self.register_event_type("on_drag_finish")
self.register_event_type("on_motion_over")
self.register_event_type("on_motion_out")
self._dragged = False
self._dragable = True
self._fired_already = False
def set_dragable(self, value):
self._dragable = value
def set_remove_on_drag(self, value):
"""
This function sets the property that determines whether the dragged widget is just copied from its parent or taken from its parent
#param value: either True or False. If True then the widget will disappear from its parent on drag, else the widget will jsut get copied for dragging
"""
self.remove_on_drag = value
def set_bound_axis_positions(self):
for obj in self.bound_zone_objects:
try:
if self.max_y < obj.y+obj.size[1]-self.size[1]:
self.max_y = obj.y+obj.size[1]-self.size[1]
except AttributeError:
self.max_y = obj.y+obj.size[1]-self.size[1]
try:
if self.max_x < obj.x+obj.size[0]-self.size[0]:
self.max_x = obj.x + obj.size[0]-self.size[0]
except AttributeError:
self.max_x = obj.x+obj.size[0]-self.size[0]
try:
if self.min_y > obj.y:
self.min_y = obj.y
except AttributeError:
self.min_y = obj.y
try:
if self.min_x > obj.x:
self.min_x = obj.x
except AttributeError:
self.min_x = obj.x
def on_touch_down(self, touch):
if self.collide_point(touch.x, touch.y) and self._dragable:
# detect if the touch is short - has time and end (if not dispatch drag)
if abs(touch.time_end - touch.time_start) > 0.2:
self.dispatch("on_drag_start")
def on_touch_up(self, touch):
if self._dragable and self._dragged:
self.short_touch = True
self.dispatch("on_drag_finish")
self.short_touch = False
def on_touch_move(self, touch):
if self._dragged and self._dragable:
x = touch.x
y = touch.y
try:
if touch.x < self.min_x:
x = self.min_x
if touch.x > self.max_x:
x = self.max_x
if touch.y < self.min_y:
y = self.min_y
if touch.y > self.max_y:
y = self.max_y
except AttributeError:
pass
self.pos = (x, y)
def easy_access_dnd(self, function_to_do, function_to_do_out, arguments = [], bind_functions = []):
"""
This function enables something that can be used instead of drag n drop
#param function_to_do: function that is to be called when mouse_over event is fired on the widget
#param bind_functions: what is really to be done - background function for GUI functionality
"""
Window.bind(mouse_pos=self.on_motion)
self.easy_access_dnd_function = function_to_do
self.easy_access_dnd_function_out = function_to_do_out
self.easy_access_dnd_function_aguments = arguments
self.easy_access_dnd_function_binds = bind_functions
def on_motion(self, etype, moutionevent):
if self.collide_point(Window.mouse_pos[0], Window.mouse_pos[1]):
if not self._fired_already:
self.dispatch("on_motion_over")
else:
self.dispatch("on_motion_out")
def on_motion_over(self):
self.easy_access_dnd_function(
self.easy_access_dnd_function_aguments,
self.easy_access_dnd_function_binds)
self._fired_already = True
def on_motion_out(self):
try:
self.easy_access_dnd_function_out()
except AttributeError:
pass
self._fired_already = False
def on_drag_start(self):
print ('drag start')
self.opacity = self.drag_opacity
self.set_bound_axis_positions()
self._old_drag_pos = self.pos
self._old_parent = self.parent
self._old_index = self.parent.children.index(self)
self._dragged = True
if self.remove_on_drag:
self.reparent(self)
else:
#create copy of object to drag
self.reparent(self)
# the final child class MUST implement __deepcopy__
# IF self.remove_on_drag == False !!! In this case this is
# met in DragableArhellModelImage class
copy_of_self = copy.deepcopy(self)
self._old_parent.add_widget(copy_of_self, index=self._old_index)
def on_drag_finish(self):
print ('drag finish')
if self._dragged and self._dragable:
self.opacity = 1.0
dropped_ok = False
for obj in self.droppable_zone_objects:
if obj.collide_point(*self.pos):
dropped_ok = True
if dropped_ok:
self.drop_func(*self.drop_args)
anim = Animation(opacity=0, duration=0.7, t="in_quad")
anim.bind(on_complete=self.deparent)
anim.start(self)
else:
anim = Animation(pos=self._old_drag_pos, duration=0.7, t="in_quad")
if self.remove_on_drag:
anim.bind(on_complete = self.reborn)
else:
anim.bind(on_complete = self.deparent)
anim.start(self)
self._dragged = False
def deparent(self, widget="dumb", anim="dumb2"):
self.get_root_window().remove_widget(self)
def on_being_dragged(self):
print ('being dragged')
def reborn(self, widget, anim):
self.deparent()
self._old_parent.add_widget(self, index=self._old_index)
def reparent(self, widget):
parent = widget.parent
orig_size = widget.size
if parent:
parent.remove_widget(widget)
parent.get_root_window().add_widget(widget)
widget.size_hint = (None, None)
widget.size = orig_size
DragableButton.py
Created on Oct 24, 2012
#author: Pavel Kostelnik
'''
from DragNDropWidget import DragNDropWidget
from kivy.uix.button import Button
class DragableButton(Button, DragNDropWidget):
'''
classdocs
'''
def __init__(self, **kw):
'''
Constructor
'''
#Button.__init__(self, **kw)
super(DragableButton, self).__init__(**kw)
self.size_hint = (None, None)
def __deepcopy__(self, dumb):
return DragableButton(text=self.text,
droppable_zone_objects=self.droppable_zone_objects,
bound_zone_objects=self.bound_zone_objects,
drag_opacity=self.drag_opacity,
drop_func=self.drop_func,
remove_on_drag=self.remove_on_drag)
arrastrar.py
__all__ = ('Relaciona3x2', 'Relaciona4x2', 'Relaciona5x2')
import kivy
kivy.require('1.0.6')
from DragableButton import DragableButton
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
Builder.load_file('arrastrar.kv')
class Relaciona3x2(Screen):
pass
class Relaciona4x2(Screen):
pass
class Relaciona5x2(Screen):
pass
class ArrastraApp(App):
def build(self):
#Window.fullscreen = 'auto'
return Relaciona3x2()
def greet(self):
print('Draggin done!')
if __name__ == "__main__":
ArrastraApp().run()
arrastrar.kv
#:kivy 1.9.0
<Relaciona3x2>:
canvas.before:
Rectangle:
pos: self.pos
size: self.size
source: 'data/img/fondobosque.jpg'
BoxLayout:
orientation: 'vertical'
BoxLayout:
id: from_box
DragableButton:
canvas:
Rectangle:
pos: self.pos
size: self.size
source: 'data/img/letra_i/iglu.png'
bound_zone_objects: [from_box, to_box ]
droppable_zone_objects: [to_box, ]
drop_func: app.greet
DragableButton:
canvas:
Rectangle:
pos: self.pos
size: self.size
source: 'data/img/letra_i/indio.png'
bound_zone_objects: [from_box, to_box, ]
droppable_zone_objects: [to_box, ]
drop_func: app.greet
DragableButton:
canvas:
Rectangle:
pos: self.pos
size: self.size
source: 'data/img/letra_m/moto.png'
bound_zone_objects: [from_box, to_box, ]
#droppable_zone_objects: [to_box, ]
drop_func: app.greet
BoxLayout:
id: from_box
DragableButton:
canvas:
Rectangle:
pos: self.pos
size: self.size
source: 'data/img/letra_u/una.png'
bound_zone_objects: [from_box, to_box, ]
#droppable_zone_objects: [to_box, ]
drop_func: app.greet
DragableButton:
canvas:
Rectangle:
pos: self.pos
size: self.size
source: 'data/img/letra_i/iman_1.png'
bound_zone_objects: [from_box, to_box, ]
droppable_zone_objects: [to_box, ]
drop_func: app.greet
DragableButton:
canvas:
Rectangle:
pos: self.pos
size: self.size
source: 'data/img/letra_u/urraca.png'
bound_zone_objects: [from_box, to_box, ]
#droppable_zone_objects: [to_box, ]
drop_func: app.greet
Image:
id: to_box
source: "data/img/caldero.png"
<Relaciona4x2>:
AnchorLayout:
Image:
source: "data/img/fondobosque.jpg"
allow_stretch: True
keep_ratio: False
<Relaciona5x2>:
AnchorLayout:
Image:
source: "data/img/fondobosque.jpg"
allow_stretch: True
keep_ratio: False
Related
Below I have a code that displays 2 scatter widgets in a kivy screen. If one widget is dragged, the other also drags and vice versa.
What I want is in addition to movement, if one is rotated, the other rotates in the same way. In other words, they are exactly mimicking each other with both rotation and position.
The problem I am facing is that kivy's default position is the bottom left of the widget. So, if the user rotates one widget from some random axis point, the other widget rotates at the bottom left. When introducing rotation while the positions are locked, it becomes all messed up, and I cannot find a way to overcome this bug in order to match both rotation and movement.
Here is code with positions locked.
from kivy.uix.scatter import Scatter
from kivy.uix.relativelayout import RelativeLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.lang import Builder
class ScatterWidget(Scatter):
pass
class SquareWidget(Widget):
pass
class WindowManager(ScreenManager):
pass
class GeneralWindow(Screen,RelativeLayout):
start_position_1=(200,200)
start_position_2=(300,400)
def on_touch_move(self, touch):
app=self.manager.get_screen('General')
pos1=app.ids['widget10'].parent.to_parent(*self.pos)
pos2=app.ids['widget20'].parent.to_parent(*self.pos)
mov1=(pos1[0]-self.start_position_1[0],pos1[1]-self.start_position_1[1])
mov2=(pos2[0]-self.start_position_2[0],pos2[1]-self.start_position_2[1])
if self.ids.one.collide_point(*touch.pos):
app.ids['two'].pos=(mov1[0]+self.start_position_2[0],mov1[1]+self.start_position_2[1])
if self.ids.two.collide_point(*touch.pos):
app.ids['one'].pos =(mov2[0]+self.start_position_1[0],mov2[1]+self.start_position_1[1])
KV = Builder.load_string("""
WindowManager:
GeneralWindow:
<GeneralWindow>:
name: 'General'
RelativeLayout:
canvas:
Color:
rgba: 0,0,0,0.3
Rectangle:
size: self.size
pos: self.pos
ScatterWidget:
do_scale: False
id: one
size_hint: None,None
size: widget10.size
rotation: 0
pos: root.start_position_1
SquareWidget:
id: widget10
size: (200,20)
canvas:
Color:
rgba: 1,1,0,0.7
Rectangle:
size: self.size
pos: self.pos
ScatterWidget:
do_scale: False
id: two
size_hint: None,None
size: widget20.size
rotation: 0
pos: root.start_position_2
SquareWidget:
id: widget20
size: (200,20)
canvas:
Color:
rgba: 0,1,0,0.7
Rectangle:
size: self.size
pos: self.pos
""")
class TApp(App):
def build(self):
return KV
if __name__ == '__main__':
TApp().run()
The Scatter widget has a rotation property, that performs a rotation about the center of the widget. By using this property, and specifying do_rotation: False for both of your ScatterWidgets, I think your problem is simplified. Also, modifying your movement code to work on the center property of the widgets adds another simplification.
Here is a modified version of your code that does this. In this code, the left button is used to move the widgets and the right button is used to rotate the widgets:
from kivy.config import Config
Config.set('input', 'mouse', 'mouse,disable_multitouch')
from kivy.app import App
from kivy.uix.scatter import Scatter
from kivy.uix.relativelayout import RelativeLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.widget import Widget
from kivy.lang import Builder
from kivy.vector import Vector
class ScatterWidget(Scatter):
def on_touch_down(self, touch):
if self.collide_point(*touch.pos) and touch.button == 'right':
touch.grab(self)
return True
return super(ScatterWidget, self).on_touch_down(touch)
def on_touch_move(self, touch):
if touch.grab_current is self and touch.button == 'right':
self.rotation = -Vector(1, 0).angle(Vector(touch.pos) - Vector(self.center))
return True
return super(ScatterWidget, self).on_touch_move(touch)
def on_touch_up(self, touch):
if touch.grab_current is self:
touch.ungrab(self)
return super(ScatterWidget, self).on_touch_up(touch)
class SquareWidget(Widget):
pass
class WindowManager(ScreenManager):
pass
class GeneralWindow(Screen, RelativeLayout):
start_center_1 = (200, 200)
start_center_2 = (300, 400)
def on_touch_move(self, touch):
app = self.manager.get_screen('General')
scatter_one = app.ids['one']
scatter_two = app.ids['two']
center1 = scatter_one.center
center2 = scatter_two.center
mov1 = (center1[0] - self.start_center_1[0], center1[1] - self.start_center_1[1])
mov2 = (center2[0] - self.start_center_2[0], center2[1] - self.start_center_2[1])
# convert touch.grab_list of WeakReferences to a grab_list of actual objects
grab_list = []
for wr in touch.grab_list:
grab_list.append(wr())
if scatter_one in grab_list:
if touch.button == 'right':
scatter_two.rotation = scatter_one.rotation
else:
scatter_two.center = (mov1[0] + self.start_center_2[0], mov1[1] + self.start_center_2[1])
elif scatter_two in grab_list:
if touch.button == 'right':
scatter_one.rotation = scatter_two.rotation
else:
scatter_one.center = (mov2[0] + self.start_center_1[0], mov2[1] + self.start_center_1[1])
return super(GeneralWindow, self).on_touch_move(touch)
KV = Builder.load_string("""
WindowManager:
GeneralWindow:
<GeneralWindow>:
name: 'General'
RelativeLayout:
canvas:
Color:
rgba: 0,0,0,0.3
Rectangle:
size: self.size
pos: self.pos
ScatterWidget:
do_scale: False
do_rotation: False
id: one
size_hint: None,None
size: widget10.size
rotation: 0
center: root.start_center_1
SquareWidget:
id: widget10
size: (200,20)
canvas:
Color:
rgba: 1,1,0,0.7
Rectangle:
size: self.size
pos: self.pos
ScatterWidget:
do_scale: False
do_rotation: False
id: two
size_hint: None,None
size: widget20.size
rotation: 0
center: root.start_center_2
SquareWidget:
id: widget20
size: (200,20)
canvas:
Color:
rgba: 0,1,0,0.7
Rectangle:
size: self.size
pos: self.pos
""")
class TApp(App):
def build(self):
return KV
if __name__ == '__main__':
TApp().run()
I have a music app written with kivymd that scans your default download directory and extracts information about your audio files. (Basically retrieving the IDV3 tags and other things)
I only have 20 audio files at max on my desktop which shouldn't be a problem but I decided to test my program with many audio (like 120) files and the results were horrible.
The interface was so slow that it barely worked. How am I supposed to display large set of widgets in kivy without causing such catastrophic performance degradation?
Also, how can I make the loading of the widgets asynchronous so that they don't take up a lot of time on startup as well?
A minimal reproducible example of my code:
import os
from kivy.lang import Builder
from kivy.uix.behaviors import ButtonBehavior
from kivy.properties import ObjectProperty
from kivymd.uix.behaviors import RectangularRippleBehavior
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.app import MDApp
class Song:
"""Class for extracting information about an audio file."""
def __init__(self, path: str):
self._path = path
self._title = "My Song"
self._artist = "Unknown artist"
# the album cover is going to a texture retrieved from the file itself
# but in this case, I will refer to a file.
self._album_cover = "Default-Album-Cover.jpg"
#property
def path(self):
return self._path
#property
def title(self):
return self._title
#property
def artist(self):
return self._artist
#property
def album_cover(self):
return self._album_cover
class SongCard(ButtonBehavior, RectangularRippleBehavior, MDBoxLayout):
"""Custom widget for creating cards."""
song_obj = ObjectProperty(Song("dummy song"), rebind=True)
class MainApp(MDApp):
def __init__(self, **kwargs):
super(MainApp, self).__init__(**kwargs)
self.theme_cls.theme_style = "Dark"
self.kv = Builder.load_string('''
#:kivy 2.0.0
<SongCard>:
orientation: "vertical"
size_hint_y: None
height: "300dp"
radius: ("10dp",)
canvas.before:
Color:
rgba: app.theme_cls.bg_dark
RoundedRectangle:
pos: self.pos
size: self.size
radius: root.radius
FitImage:
source: root.song_obj.album_cover
MDLabel:
text: root.song_obj.title
adaptive_height: True
MDLabel:
text: root.song_obj.artist
adaptive_height: True
ScrollView:
do_scroll_x: False
MDGridLayout:
id: song_grid
cols: 2
adaptive_height: True
spacing: "10dp"
padding: "10dp"
''')
def build(self):
return self.kv
def on_start(self):
for _ in range(25):
for audio_file in os.listdir(os.path.join(os.path.expanduser('~'), "Downloads")):
if audio_file.endswith(".mp3"):
song_obj = Song(audio_file)
self.kv.ids.song_grid.add_widget(
SongCard(
song_obj=song_obj
)
)
if __name__ == '__main__':
MainApp().run()
You can use the RecycleView class to manage the amount of widgets and help your program to be able to accept a big amount of widgets, the ScrollView is great but recycleview manages data as dictionary and then you can use two BoxLayout class under a BoxLayout widget to have the GridLayout class functionality, I tested this with 213 elements and it works with a lot of widgets and good performance:
import os
from kivy.lang import Builder
from kivy.uix.behaviors import ButtonBehavior
from kivy.properties import ObjectProperty, StringProperty
from kivymd.uix.behaviors import RectangularRippleBehavior
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.app import MDApp
from kivy.uix.recyclegridlayout import RecycleGridLayout
class Song:
"""Class for extracting information about an audio file."""
def __init__(self, path: str):
self._path = path
self._title = "My Song"
self._artist = "Unknown artist"
# the album cover is going to a texture retrieved from the file itself
# but in this case, I will refer to a file.
self._album_cover = "Default-Album-Cover.jpg"
## #property
## def path(self):
## return self._path
##
## #property
## def title(self):
## return self._title
##
## #property
## def artist(self):
## return self._artist
##
## #property
## def album_cover(self):
## return self._album_cover
class SongCard(MDBoxLayout):
"""Custom widget for creating cards."""
#song_obj = ObjectProperty(Song("dummy song"), rebind=True)
song_obj = StringProperty()
class B2(ButtonBehavior, RectangularRippleBehavior, MDBoxLayout):
"""Custom widget for creating cards."""
pass
class MainApp(MDApp):
def __init__(self, **kwargs):
super(MainApp, self).__init__(**kwargs)
self.theme_cls.theme_style = "Dark"
self.kv = Builder.load_string('''
#:kivy 2.0.0
<SongCard>:
orientation: "vertical"
size_hint_y: None
height: "300dp"
#radius: ("10dp",)
BoxLayout:
padding: dp(10)
spacing: dp(5)
B2:
padding: dp(10)
canvas.before:
Color:
rgba: [0,1,0,.2]
RoundedRectangle:
pos: self.pos
size: self.size
radius: [30,]
orientation: "vertical"
spacing: dp(5)
FitImage:
size_hint: 1,1
source: "Help5/cc.png" #root.song_obj.album_cover
MDLabel:
text: root.song_obj[:40] #.title
adaptive_height: True
MDLabel:
text: " - jbsidis" #.artist
adaptive_height: True
B2:
padding: dp(10)
canvas.before:
Color:
rgba: [1,0,0,.2]
RoundedRectangle:
pos: self.pos
size: self.size
radius: [30,]
orientation: "vertical"
spacing: dp(5)
FitImage:
source: "Help5/cc.png" #root.song_obj.album_cover
MDLabel:
text: root.song_obj[:40] #.title
adaptive_height: True
MDLabel:
text: " - jbsidis" #.artist
adaptive_height: True
Screen:
BoxLayout:
orientation: "vertical"
#do_scroll_x: False
RecycleSupportGridLayoutjbsidis:
id: song_grid
key_viewclass: 'viewclass'
key_size: 'height'
RecycleBoxLayout:
padding: dp(10)
default_size: None, dp(248)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
<RecycleSupportGridLayoutjbsidis#RecycleView>:
''')
def build(self):
return self.kv
def on_start(self):
#for _ in range(25):
for audio_file in os.listdir("/media/jbsidis/Android/Q/D/Anthony Robbins/"): #os.path.join(os.path.expanduser('~'), "Downloads")
#if audio_file.endswith(".mp3"):
#song_obj = Song(audio_file)
self.kv.ids.song_grid.data.append(
{
"viewclass": "SongCard",
"song_obj": audio_file
}
)
## {
## "viewclass": "CustomOneLineIconListItem",
## "icon": name_icon,
## "text": name_icon,
## "on_release": lambda:Clipboard.copy(name_icon),
## }
## self.kv.ids.song_grid.add_widget(
## SongCard(
## song_obj=song_obj
## )
## )
if __name__ == '__main__':
MainApp().run()
Pictures:
In kivy 1.11.1. and python 3. I have in my root class a FloatLayout which contains a Label and a dropdown[text input + RecycleView], but when writing example in my text input, the text input goes up and hide my label, I would like it to be fixes and the dropdown going down. I try to dive into the refresh_view_layout method but without success. Any ideas ?? thanks for you're help :). Here is my python file :
from kivy.app import App
from kivy.uix.textinput import TextInput
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.properties import NumericProperty, ListProperty, BooleanProperty, ObjectProperty, StringProperty
from kivy.uix.recycleview import RecycleView
from kivy.uix.recyclegridlayout import RecycleGridLayout
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.label import Label
from kivy.uix.recycleboxlayout import RecycleBoxLayout
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.recycleview.layout import LayoutSelectionBehavior
class SelectableRecycleBoxLayout(FocusBehavior, LayoutSelectionBehavior,
RecycleBoxLayout):
''' Adds selection and focus behaviour to the view. '''
class SelectableLabel(RecycleDataViewBehavior, Label):
''' Add selection support to the Label '''
index = None
selected = BooleanProperty(False)
selectable = BooleanProperty(True)
def refresh_view_attrs(self, rv, index, data):
''' Catch and handle the view changes '''
self.index = index
return super(SelectableLabel, self).refresh_view_attrs(
rv, index, data)
def on_touch_down(self, touch):
''' Add selection on touch down '''
if super(SelectableLabel, self).on_touch_down(touch):
return True
if self.collide_point(*touch.pos) and self.selectable:
self.parent.parent.parent.children[1].text = self.text # ajout test
return self.parent.select_with_touch(self.index, touch)
def apply_selection(self, rv, index, is_selected):
''' Respond to the selection of items in the view. '''
self.selected = is_selected
if is_selected:
print("selection changed to {0}".format(rv.data[index]))
class RV(RecycleView):
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
class DropDownWidget(BoxLayout):
txt_input = ObjectProperty()
rv = ObjectProperty()
class MyTextInput(TextInput):
txt_input = ObjectProperty()
flt_list = ObjectProperty()
word_list = ListProperty()
#this is the variable storing the number to which the look-up will start
starting_no = NumericProperty(1)
suggestion_text = ''
def __init__(self, **kwargs):
super(MyTextInput, self).__init__(**kwargs)
def on_text(self, instance, value):
#find all the occurrence of the word
self.parent.ids.rv.data = []
matches = [self.word_list[i] for i in range(len(self.word_list)) if self.word_list[i][:self.starting_no] == value[:self.starting_no]]
#display the data in the recycleview
display_data = []
for i in matches:
display_data.append({'text':i})
self.parent.ids.rv.data = display_data
#ensure the size is okay
if len(matches) <= 10:
self.parent.height = (50 + (len(matches)*20))
else:
self.parent.height = 240
def keyboard_on_key_down(self, window, keycode, text, modifiers):
if self.suggestion_text and keycode[1] == 'tab':
self.insert_text(self.suggestion_text + ' ')
return True
return super(MyTextInput, self).keyboard_on_key_down(window, keycode, text, modifiers)
class Body(FloatLayout):
def __init__(self, **kwargs):
super(Body, self).__init__(**kwargs)
self.add_widget(Label(text = 'This Label is hidden by the dropdown if you type in the text input : "row" or "example" \n and I would like the Text Input to be fixed and not going up when changing text',
pos_hint = {'center_x':.5,'center_y':.6},
color = [100,100,100,1]))
widget_1 = DropDownWidget(pos_hint = {'center_x':.5,'center_y':.5}, \
size_hint = (None, None), size = (600, 60))
widget_1.ids.txt_input.word_list = ['row1', 'row2', 'row3', 'row4', 'row5', 'line1','line2',
'example1','example2','example3','example4','example5','example6','example7','example8','example9','example10']
self.add_widget(widget_1)
class MyApp(App):
def build(self):
return Body()
if __name__ == "__main__":
MyApp().run()
and my kv file :
<Body>:
canvas:
Color:
rgba:(1, 1, 1, 1) # white
Rectangle:
pos: self.pos
size: self.size
<DropDownWidget>:
canvas:
Color:
rgba:(1, 1, 1, 1)
Rectangle:
pos: self.pos
size: self.size
orientation: 'vertical'
spacing: 2
txt_input: txt_input
rv: rv
MyTextInput:
id: txt_input
size_hint_y: None
height: 50
RV:
id: rv
<MyTextInput>:
readonly: False
multiline: False
<SelectableLabel>:
# Draw a background to indicate selection
id: selabel
color: 0,0,0,1
canvas.before:
Color:
rgba: (0, 0, 1, .5) if self.selected else (1, 1, 1, 1)
Rectangle:
pos: self.pos
size: self.size
<RV>:
canvas:
Color:
rgba: 0,0,0,.2
Line:
rectangle: self.x +1 , self.y, self.width - 2, self.height -2
bar_width: 10
scroll_type:['bars']
viewclass: 'SelectableLabel'
SelectableRecycleBoxLayout:
default_size: None, dp(20)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
multiselect: False
Actually, when I run your code, I don't see the behavior that you described. But if you add the Label and DropDownWidget to the kv, you can take advantage of the bindings that kv sets up for you. Here is how I might do this (there are lots of options). First, I added a ListProperty to the DropDownWidget to hold the word_list:
class DropDownWidget(BoxLayout):
txt_input = ObjectProperty()
rv = ObjectProperty()
word_list = ListProperty([])
And the Body class can just be:
class Body(FloatLayout):
pass
Then, the beginning of the kv can be:
<Body>:
canvas:
Color:
rgba:(1, 1, 1, 1) # white
Rectangle:
pos: self.pos
size: self.size
Label:
id: lab
text: 'This Label is hidden by the dropdown if you type in the text input : "row" or "example"'
pos_hint: {'center_x':.5,'center_y':.6}
color: [100,100,100,1]
size_hint: None, None
size: self.texture_size # use minimal size for the Label
DropDownWidget:
pos_hint: {'center_x':.5}
y: lab.y - self.height # position DropDownWidget just below the Label
size_hint: None, None
size: 600, 60
word_list: ['row1', 'row2', 'row3', 'row4', 'row5', 'line1','line2', 'example1','example2','example3','example4','example5','example6','example7','example8','example9','example10']
<DropDownWidget>:
canvas:
Color:
rgba:(1, 1, 1, 1)
Rectangle:
pos: self.pos
size: self.size
orientation: 'vertical'
spacing: 2
txt_input: txt_input
rv: rv
MyTextInput:
id: txt_input
size_hint_y: None
height: 50
word_list: root.word_list # use the word_list from DropDownWidget
RV:
id: rv
The remainder of the kv is unchanged.
I am facing an Attribute error in the code below. I am new to this kivy framework. When I tried to call the Clock.schedule_interval function to infinite scroll the image. Clock.schedule_interval(self.root.ids.mainwindow.scroll_textures, 1/60.)
AttributeError: 'NoneType' object has no attribute 'ids'
This error popped up.
The .py file
import kivy
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.widget import Widget
from kivy.properties import ObjectProperty
from kivy.uix.image import Image
from kivy.core.window import Window
from kivy.clock import Clock
from kivy.uix.button import Button
from kivy.core.audio import SoundLoader
from kivy.config import Config
# You can create your kv code in the Python file
# Create a class for all screens in which you can include
# helpful methods specific to that screen
kv = Builder.load_file("main.kv")
class MainWindow(Screen):
cloud_texture = ObjectProperty(None)
floor_texture = ObjectProperty(None)
sound = ObjectProperty(None)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.cloud_texture = Image(source = "cloud4.png").texture
self.cloud_texture.wrap = 'repeat'
self.cloud_texture.uvsize = (Window.width/self.cloud_texture.width,-1)
self.floor_texture = Image(source = "floor2.jpg").texture
self.floor_texture.wrap = 'repeat'
self.floor_texture.uvsize = (Window.width/self.floor_texture.width,-1)
self.sun_texture = Image(source = "sun.png").texture
self.sun_texture.uvsize = (Window.width/self.sun_texture.width,-1)
self.sound = SoundLoader.load('8bitmenu.wav')
self.sound.play()
def scroll_textures(self, time_passed):
#Update the uvpos
self.cloud_texture.uvpos = ((self.cloud_texture.uvpos[0] - time_passed/20) % Window.width, self.cloud_texture.uvpos[1])
self.floor_texture.uvpos = ((self.floor_texture.uvpos[0] + time_passed/8) % Window.width, self.floor_texture.uvpos[1])
#Redraw textures
texture = self.property('cloud_texture')
texture.dispatch(self)
texture = self.property('floor_texture')
texture.dispatch(self)
class Window1(Screen):
pass
class WindowManager(ScreenManager):
pass
#Config.write()
class MyApp(App):
def on_start(self):
Clock.schedule_interval(self.root.ids.mainwindow.scroll_textures, 1/60.)
pass
def build(self):
return kv
MyApp().run()
.kv file
<WindowManager>:
MainWindow:
Window1:
<MainWindow>:
name: "main"
id: mainwindow
canvas.before:
Rectangle:
size: self.size
pos: self.pos
source: "sky.jpg"
Rectangle:
size: self.width - 1000, 120
pos: self.pos[0] + 500, self.pos[1] + self.height - 138
texture: self.sun_texture
Rectangle:
size: self.width, 138
pos: self.pos[0], self.pos[1] + self.height - 168
texture: self.cloud_texture
Rectangle:
size: self.width, 100
pos: self.pos[0], self.pos[1]
texture: self.floor_texture
Image:
source: 'dog2.gif'
anim_delay: 0.09
size: self.width, self.height
pos: -270, -270
Image:
source: 'boy1.gif'
anim_delay: 0.09
size: self.width, self.height
pos: -100, -230
Button:
text: "Start your journey"
color: .8,.9,0,1
background_color: 1, 1, 1, 0.3
halign: 'center'
center: root.center
font_size: 12
size: 32, 32
size_hint: .2, .2
pos: 300, 250
on_release:
app.root.current = "win1"
<Window1>:
name: "win1"
Button:
text: "Go back"
on_release:
app.root.current = "main"
Can anyone help me with this?
I solved the Error here. Just declared the id in parent class and that worked
WindowManager:
MainWindow:
id: mainwindow
Window1:
Within the program, one can see that as the ball bounces around, one has the ability to open settings page from the top right corner. Doing so pauses the ball's motion and opens the settings page.
Sorry if this is an obvious question, but I am again stumped by the strange inner workings of Kivy, and the docs aren't much use on these types of issues.
Problem
Ball always start at centre position. Want ball to continue / resume from previous position before switching screen?
Steps to recreate problem
Click on label, "Tap to start". Ball started bouncing from centre position
Click on cogwheel image. Settings screen is displayed
Click on "x" to close Settings screen. Ball started bouncing from centre position.
Code:
from kivy.lang import Builder
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.image import Image
from kivy import Config
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition,\
SlideTransition
from kivy.uix.widget import Widget
from kivy.animation import Animation
from kivy.properties import NumericProperty, ReferenceListProperty,\
ObjectProperty
from kivy.clock import Clock
from kivy.vector import Vector
from random import randint
Builder.load_string('''
<Ball>:
size_hint: None, None
source: '58-Breakout-Tiles.png'
pos: self.pos
size: 15, 15
<SettingsScreen>:
close: close
AnchorLayout:
anchor_x: 'left'
anchor_y: 'top'
Image:
id: close
size_hint: .03, .03
source: 'grey_crossGrey.png'
GridLayout:
cols: 2
Label:
font_name: 'vgafix.fon'
text: 'Music: '
Switch:
active: True
Label:
font_name: 'vgafix.fon'
text: 'Sounds: '
Switch:
active: True
<MenuScreen>:
cog: cog
AnchorLayout:
anchor_x: 'right'
anchor_y: 'top'
Image:
id: cog
size_hint: .03, .03
source: 'settings-cog.png'
BoxLayout:
orientation: 'vertical'
Image:
source: 'brickbreaker log.png'
Label:
font_name: 'vgafix.fon'
text: 'Tap to start'
<GameScreen>:
ball: ball
cog: cog
AnchorLayout:
anchor_x: 'right'
anchor_y: 'top'
Image:
id: cog
size_hint: .03, .03
source: 'settings-cog.png'
Ball:
id: ball
size_hint: None, None
center: self.parent.center
''')
Config.set('graphics', 'multisamples', '0')
class Ball(Image):
velocityX, velocityY = NumericProperty(0), NumericProperty(0)
velocity = ReferenceListProperty(velocityX, velocityY)
def move(self):
self.pos = Vector(*self.velocity) + self.pos
class Player(Widget):
pass
class Brick(Widget):
pass
class SettingsScreen(Screen):
def __init__(self, **kwargs):
super(SettingsScreen, self).__init__(**kwargs)
self.previous = False
def on_touch_down(self, touch):
if self.close.collide_point(*touch.pos):
sm.transition = SlideTransition(direction = 'right')
if self.previous == 'game':
sm.get_screen('game').interval()
sm.current = self.previous
class MenuScreen(Screen):
def __init__(self, **kwargs):
super(MenuScreen, self).__init__(**kwargs)
def on_touch_down(self, touch):
if self.cog.collide_point(*touch.pos):
sm.transition = SlideTransition(direction = 'left')
sm.get_screen('settings').previous = 'menu'
sm.current = 'settings'
else:
sm.transition = FadeTransition()
sm.current = 'game'
class GameScreen(Screen):
def __init__(self, **kwargs):
super(GameScreen, self).__init__(**kwargs)
self.initBall()
self.interval = Clock.schedule_interval(self.update, 1.0/60.0)
def on_touch_down(self, touch):
if self.cog.collide_point(*touch.pos):
sm.transition = SlideTransition(direction = 'left')
sm.get_screen('settings').previous = 'game'
self.interval.cancel()
sm.current = 'settings'
def initBall(self):
self.ball.center = self.center
self.ball.velocity = Vector(0, 4).rotate(randint(0, 360))
def update(self, dt):
self.ball.move()
if (self.ball.y < 0) or (self.ball.y > self.height-15):
self.ball.velocityY *= -1
# bounce off left and right
if (self.ball.x < 0) or (self.ball.x > self.width-15):
self.ball.velocityX *= -1
sm = ScreenManager(transition = FadeTransition())
sm.add_widget(MenuScreen(name='menu'))
sm.add_widget(GameScreen(name='game'))
sm.add_widget(SettingsScreen(name='settings'))
class BrickBreakerInsanityApp(App):
def build(self):
return sm
if __name__ == '__main__':
BrickBreakerInsanityApp().run()
Code assets (required):
https://drive.google.com/open?id=1GAnv5DfjNUuAXTybmsan90Dm0OuSVOfb
https://i.stack.imgur.com/rR799.png
https://i.stack.imgur.com/ngYvL.png
https://i.stack.imgur.com/AuxI3.png
https://i.stack.imgur.com/ypd7C.png
Root cause
The SlideTransition and direction is causing the ball to start at the centre.
Solution
Remove all references to SlideTransition.
Example
from kivy.lang import Builder
from kivy.app import App
from kivy.uix.image import Image
from kivy import Config
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition,
from kivy.uix.widget import Widget
from kivy.properties import NumericProperty, ReferenceListProperty, StringProperty
from kivy.clock import Clock
from kivy.vector import Vector
from random import randint
Builder.load_string('''
<Ball>:
size_hint: None, None
source: './assets/icons/58-Breakout-Tiles.png'
pos: self.pos
size: 15, 15
<SettingsScreen>:
close: close
AnchorLayout:
anchor_x: 'left'
anchor_y: 'top'
Image:
id: close
size_hint: .03, .03
source: './assets/icons/grey_crossGrey.png'
GridLayout:
cols: 2
Label:
font_name: "./assets/fonts/vgafix.fon"
text: 'Music: '
Switch:
active: True
Label:
font_name: "./assets/fonts/vgafix.fon"
text: 'Sounds: '
Switch:
active: True
<MenuScreen>:
cog: cog
AnchorLayout:
anchor_x: 'right'
anchor_y: 'top'
Image:
id: cog
size_hint: .03, .03
source: './assets/icons/settings-cog.png'
BoxLayout:
orientation: 'vertical'
Image:
source: "./assets/icons/brickbreaker log.png"
Label:
font_name: "./assets/fonts/vgafix.fon"
text: 'Tap to start'
<GameScreen>:
ball: ball
cog: cog
AnchorLayout:
anchor_x: 'right'
anchor_y: 'top'
Image:
id: cog
size_hint: .03, .03
source: './assets/icons/settings-cog.png'
Ball:
id: ball
size_hint: None, None
center: self.parent.center
''')
Config.set('graphics', 'multisamples', '0')
class Ball(Image):
velocityX, velocityY = NumericProperty(0), NumericProperty(0)
velocity = ReferenceListProperty(velocityX, velocityY)
def move(self):
self.pos = Vector(*self.velocity) + self.pos
class Player(Widget):
pass
class Brick(Widget):
pass
class SettingsScreen(Screen):
def __init__(self, **kwargs):
super(SettingsScreen, self).__init__(**kwargs)
self.previous = StringProperty('')
def on_touch_down(self, touch):
if self.close.collide_point(*touch.pos):
self.manager.current = self.previous
class MenuScreen(Screen):
def on_touch_down(self, touch):
if self.cog.collide_point(*touch.pos):
self.manager.get_screen('settings').previous = self.manager.current
self.manager.current = 'settings'
else:
self.manager.transition = FadeTransition()
self.manager.current = 'game'
class GameScreen(Screen):
def __init__(self, **kwargs):
super(GameScreen, self).__init__(**kwargs)
self.initBall()
def on_pre_enter(self, *args):
self.interval = Clock.schedule_interval(self.update, 1.0 / 60.0)
def on_touch_down(self, touch):
if self.cog.collide_point(*touch.pos):
self.manager.get_screen('settings').previous = self.manager.current
self.manager.current = 'settings'
def initBall(self):
self.ball.center = self.center
self.ball.velocity = Vector(0, 4).rotate(randint(0, 360))
def update(self, dt):
self.ball.move()
if (self.ball.y < 0) or (self.ball.y > self.height - 15):
self.ball.velocityY *= -1
# bounce off left and right
if (self.ball.x < 0) or (self.ball.x > self.width - 15):
self.ball.velocityX *= -1
def on_pre_leave(self, *args):
self.interval.cancel()
sm = ScreenManager(transition=FadeTransition())
sm.add_widget(MenuScreen(name='menu'))
sm.add_widget(GameScreen(name='game'))
sm.add_widget(SettingsScreen(name='settings'))
class BrickBreakerInsanityApp(App):
def build(self):
return sm
if __name__ == '__main__':
BrickBreakerInsanityApp().run()