binding movement and rotation of kivy scatter widgets - python

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()

Related

Reference attribute of element that is embedded in canvas (Kivy)

I am trying to reference the source image of a Rectangle object in a canvas. I don't seem to find the correct code that will correctly reference it. Trying with ids doesn't work because the rectangle cant be given an id, only a group. Therefore, I need to use a group, but this is complex because I have several embedded attributes. Can anyone help? The current solution using self.canvas.get_group("firstQelement")[0].source I found online when a user had an issue with a similar problem. I have tried using different paths to get to the group, including referencing ids and layouts.
AnswerCycle refers to a pre formatted toggle button
Kivy File:
<QuestionDisplay>:
name: "questionDisplay"
RelativeLayout:
canvas.before:
Color:
rgba: utils.get_color_from_hex('#90E0EF')
Rectangle:
pos: self.pos
size: self.size
GridLayout:
padding: 20
spacing: 20
cols: 1
size: root.width, root.height
Title:
id: questionTitle
Image:
canvas.before:
Color:
rgba: utils.get_color_from_hex('#0077B6')
Rectangle:
size: self.size
pos: self.pos
id: mainQuestion
source: "empty.png"
size_hint_y: None
size: root.width, 195
GridLayout:
size_hint_y: None
size: root.width, 200
cols: 2
AnswerCycle:
id: firstQ
canvas.after:
Rectangle
group: "firstQelement"
pos: self.pos
size: self.size
source: "empty.png"
AnswerCycle:
id: secondQ
canvas.after:
Rectangle
group: "secondQelement"
pos: self.pos
size: self.size
source: "empty.png"
AnswerCycle:
id: fourthQ
canvas.after:
Rectangle
group: "thirdQelement"
pos: self.pos
size: self.size
source: "empty.png"
AnswerCycle:
id: thirdQ
canvas.after:
Rectangle
group: "fourthQelement"
pos: self.pos
size: self.size
source: "empty.png"
Python Code:
if Value.correctButtonNumber == 1: self.canvas.get_group("firstQelement")[0].source = "cAnswer.png"
if Value.correctButtonNumber == 2: self.canvas.get_group("secondQelement")[0].source = "cAnswer.png"
if Value.correctButtonNumber == 3: self.canvas.get_group("thirdQelement")[0].source = "cAnswer.png"
if Value.correctButtonNumber == 4: self.canvas.get_group("fourthQelement")[0].source = "cAnswer.png"
Error:
if Value.correctButtonNumber == 1: self.canvas.get_group("firstQelement")[0].source = "cAnswer.png"
IndexError: list index out of range
Minimal Reproducible Example:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
import random
APP_KV = """
<CanvasTest>:
BoxLayout:
canvas.after:
Color:
rgba: 0, 1, 0, 1
Rectangle:
group: 'rectangle'
size: 400, 200
pos: self.pos
Color:
rgba: 1, 0, 0, 1
Ellipse:
group: 'ellipse'
size: 200, 100
pos: self.pos
"""
class CanvasTest(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
print(self.BoxLayout.canvas.after.get_group('rectangle'))
class MainApp(App):
def build(self):
self.root = Builder.load_string(APP_KV)
return CanvasTest()
if __name__ == '__main__':
MainApp().run()
When BoxLayout: is removed, from both the KV script and the print statement, this code works correctly.
Here is a modified version of your code that changes the source attribute of the Rectangle:
from kivy.app import App
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
APP_KV = """
<CanvasTest>:
BoxLayout:
id: box
canvas.after:
Color:
rgba: 0, 1, 0, 1
Rectangle:
group: 'rectangle'
size: 400, 200
pos: self.pos
Color:
rgba: 1, 0, 0, 1
Ellipse:
group: 'ellipse'
size: 200, 100
pos: self.pos
"""
class CanvasTest(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
Clock.schedule_once(self.info, 2)
def info(self, dt):
rect = self.ids.box.canvas.after.get_group('rectangle')[0]
rect.source = 'tester.png'
class MainApp(App):
def build(self):
self.root = Builder.load_string(APP_KV)
return CanvasTest()
if __name__ == '__main__':
MainApp().run()
I added the id of box to the BoxLayout in the APP_KV to allow access to that BoxLayout. Note that you shouldn't try to access ids in the __init__() method because they typically have not been set yet at that point. That is why I used Clock.schedule_once().

Image that disappears when dragging it to a target on Kivy

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

Kivy GUI - fill color in circle like water fill in circle container level wise

As shown in the image above I need to code a .kv file in the Kivy GUI so I can show a circle filling with color as a value increase. For 0 value it is empty as value increase circle is fill with color.
How can I do that?
That kind of effect can be done by creating a mask for this you can use Stencil instructions as shown below:
from kivy.app import App
from kivy.lang import Builder
main_widget_kv = '''
BoxLayout:
border: 10
WaterFill:
level: slider.value_normalized
color: 1, 1, 0
Slider:
id: slider
orientation: 'vertical'
value: 50
<WaterFill#Widget>:
level: 0.1
width: self.height
size_hint: None, 1
color: 0, 0, 1
canvas:
StencilPush
Ellipse:
pos: root.pos
size: root.size
StencilUse
Color:
rgb: root.color
Rectangle:
pos: root.pos
size: (root.width, root.level*root.height)
StencilUnUse
StencilPop
'''
class TestApp(App):
def build(self):
return Builder.load_string(main_widget_kv)
if __name__ == '__main__':
TestApp().run()
Update:
The following example shows an example of how to update the height value using a method with the help of Clock:
from kivy.app import App
from kivy.clock import Clock
from kivy.uix.widget import Widget
from kivy.uix.anchorlayout import AnchorLayout
from kivy.lang import Builder
Builder.load_string("""
<WaterFill>:
level: 0.0
width: self.height
size_hint: None, 1
color: 0, 0, 1
canvas:
StencilPush
Ellipse:
pos: root.pos
size: root.size
StencilUse
Color:
rgb: root.color
Rectangle:
pos: root.pos
size: (root.width, root.level*root.height)
StencilUnUse
StencilPop
""")
class WaterFill(Widget):
def __init__(self, *args, **kwargs):
Widget.__init__(self, *args, **kwargs)
self.delta = 0.01
Clock.schedule_interval(self.on_timeout, 0.05)
def on_timeout(self, *args):
self.level += self.delta
if self.level >= 1:
self.delta = -0.01
elif self.level <= 0:
self.delta = 0.01
class TestApp(App):
def build(self):
lay = AnchorLayout(anchor_x='center', anchor_y='center')
lay.add_widget(WaterFill())
return lay
if __name__ == "__main__":
TestApp().run()

Kivy rectangle redraw, self.pos and self.size are (0,0)

I'm using a loop to create 27 label instances. These labels need to be numbered using their Label.text method - This I've achieved.
I also need to be able to re-colour these labels (canvas rectangle) using a python function (based on other inputs). This is the bit that doesn't work
Basically, the init method of the GridLayout creates 29 instances of the custButton() Label. This loop also re-numbers (label.text) the buttons, but the re-coloring of one label does not work.
I've isolated that self.pos and self.size are both returning (0,0) in the xBackground_Color function - this is why the new rectangle doesn't draw after the old one is cleared (the clear works).
Why doesn't self.pos and self.size work from the python side?
I've tried soooooooo many things! Please help if you can.
Here's kvMain.kv :
#:kivy 1.0.9
FloatLayout:
canvas:
Color:
rgba: (64/255, 64/255, 64/255, 1) #Whole screen background
Rectangle:
pos: self.pos
size: self.size
BoxLayout:
orientation: 'vertical'
padding: 10
FloatLayout: #Dummy widget
myGrid:
<myGrid>:
cols: 9
spacing: 3
padding: 3
canvas.before:
Color:
rgba: (216/255, 216/255, 216/255, 1)
Rectangle:
pos: self.pos
size: self.size
<custButtom>:
canvas.before:
Color:
rgba: (191/255, 191/255, 191/255, 1)
Rectangle:
pos: self.pos
size: self.size
font_size: 14
color: (90/255, 90/255, 90/255, 1)
text: 'Er' #Don't ever want to see this text. Will set each label in loop
size: self.texture_size
Here's main.py:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.label import Label
from kivy.graphics.vertex_instructions import Rectangle, Line
from kivy.graphics.context_instructions import Color
IN_Labels = {}
class custButtom(Label):
def __init__(self, **kwargs):
super(custButtom, self).__init__(**kwargs)
def xtext(self, newText):
self.text = newText
self.size = self.texture_size
def xbackground_color(self, newR, newG, newB, newA):
print('IN_Labels[14] pos ('+ str(IN_Labels[14].pos[0]) + ',' + str(IN_Labels[14].pos[1]) + ') size ('+ str(IN_Labels[14].size[0]) + ',' + str(IN_Labels[14].size[1]) + ')')
print('self pos ('+ str(self.pos[0]) + ',' + str(self.pos[1]) + ') size ('+ str(self.size[0]) + ',' + str(self.size[1]) + ')')
self.canvas.before.clear()
with self.canvas.before:
Color(newR, newG, newB, newA)
#Problem is that self.size and self.pos are both (0,0)
Rectangle(size=self.size, pos=self.pos)
class myGrid(GridLayout):
def __init__(self, **kwargs):
super(myGrid, self).__init__(**kwargs)
for i in range(0, 27, 1):
IN_Labels[i] = custButtom()
self.add_widget(IN_Labels[i])
#CAN change text, needs more work to get it right
IN_Labels[i].xtext(str(i))
# I need to be able to change backgroundcolor programatically
IN_Labels[14].xbackground_color(1,0,0,1)
class MainApp(App):
def build(self):
return kvMain
kvMain = Builder.load_file("kvMain.kv")
if __name__ == '__main__':
MainApp().run()
Found it! I think the problem is that the label doesn't really have size and pos at the time at which it's being asked for self.size and self.pos.
Binding a function to resize and reposition it solves the issue.
Thanks to Alexander Taylor for his excellent YouTube videos and his blog where I found the answer: https://blog.kivy.org/2014/10/updating-canvas-instructions-declared-in-python/
kvMain.kv becomes:
#:kivy 1.0.9
FloatLayout:
canvas:
Color:
rgba: (64/255, 64/255, 64/255, 1) #Whole screen background
Rectangle:
pos: self.pos
size: self.size
BoxLayout:
orientation: 'vertical'
padding: 10
FloatLayout: #Dummy widget
myGrid:
<myGrid>:
cols: 9
spacing: 3
padding: 3
canvas.before:
Color:
rgba: (216/255, 216/255, 216/255, 1)
Rectangle:
pos: self.pos
size: self.size
<custButtom>:
#CANVAS INSTRUCTION MOVED TO PYTHON INIT
font_size: 14
color: (90/255, 90/255, 90/255, 1)
text: 'Er' #Don't ever want to see this text. Will set each label in loop
size: self.texture_size
main.py becomes:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.label import Label
from kivy.graphics.vertex_instructions import Rectangle, Line
from kivy.graphics.context_instructions import Color
IN_Labels = {}
class custButtom(Label):
def __init__(self, **kwargs):
super(custButtom, self).__init__(**kwargs)
with self.canvas.before:
Color(191/255, 191/255, 191/255, 1)
self.rect = Rectangle(pos=self.pos, size=self.size)
#Ensure that everytime the Rectangle is updated, it's repositioned correctly
self.bind(pos=self.update_rect, size=self.update_rect)
def update_rect(self, *args):
self.rect.pos = self.pos
self.rect.size = self.size
def xtext(self, newText):
self.text = newText
self.size = self.texture_size
def xbackground_color(self, newR, newG, newB, newA):
print('IN_Labels[14] pos ('+ str(IN_Labels[14].pos[0]) + ',' + str(IN_Labels[14].pos[1]) + ') size ('+ str(IN_Labels[14].size[0]) + ',' + str(IN_Labels[14].size[1]) + ')')
print('self pos ('+ str(self.pos[0]) + ',' + str(self.pos[1]) + ') size ('+ str(self.size[0]) + ',' + str(self.size[1]) + ')')
self.canvas.before.clear()
with self.canvas.before:
Color(newR, newG, newB, newA)
self.rect = Rectangle(size=self.size, pos=self.pos)
class myGrid(GridLayout):
def __init__(self, **kwargs):
super(myGrid, self).__init__(**kwargs)
for i in range(0, 27, 1):
IN_Labels[i] = custButtom()
self.add_widget(IN_Labels[i])
#CAN change text, needs more work to get it right
IN_Labels[i].xtext(str(i))
# I need to be able to change backgroundcolor programatically
IN_Labels[14].xbackground_color(1,0,0,1)
class MainApp(App):
def build(self):
return kvMain
kvMain = Builder.load_file("kvMain.kv")
if __name__ == '__main__':
MainApp().run()

Kivy - Black Screen between transitions

I'm trying to copy this gif, which is done in Kivy (here's the link to the full page )
Just as I started, I noticed a black screen between transition (link to what it looks like so you don't have to copy-paste and run)
Why does that black screen appear?
EDIT: I must work without buttons.
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
# Create both screens. Please note the root.manager.current: this is how
# you can control the ScreenManager from kv. Each screen has by default a
# property manager that gives you the instance of the ScreenManager used.
Builder.load_string("""
<MenuScreen>:
canvas.before:
Color:
rgba: 122,255,0,2
Rectangle:
pos: self.pos
size: self.size
Label:
text: 'hello'
<SettingsScreen>:
canvas.before:
Color:
rgba: 0,255,0,2
Rectangle:
pos: self.pos
size: self.size
Label:
text: 'hello'
""")
# Declare both screens
class MenuScreen(Screen):
def on_touch_down(self, touch):
sm.current = 'settings'
class SettingsScreen(Screen):
def on_touch_down(self, touch):
sm.current = 'menu'
# Create the screen manager
sm = ScreenManager()
sm.add_widget(MenuScreen(name='menu'))
sm.add_widget(SettingsScreen(name='settings'))
class TestApp(App):
def build(self):
return sm
if __name__ == '__main__':
TestApp().run()
EDIT: I've tried this but still not working
<sm>:
canvas:
Color:
rgb: (0, 255, 255)
Rectangle:
size: self.size
pos: self.pos
You are not supposed to use the Screen subclasses directly. Instead you must add a component first (e.g. Button or Layout), for example use RelativeLayout:
Builder.load_string("""
<MenuScreen>:
RelativeLayout:
canvas.before:
Color:
rgba: 122,255,0,2
Rectangle:
pos: self.pos
size: self.size
Label:
text: 'hello'
<SettingsScreen>:
RelativeLayout:
canvas.before:
Color:
rgba: 0,255,0,2
Rectangle:
pos: self.pos
size: self.size
Label:
text: 'hello'
""")
That black area is a canvas of the screen manager. If you don't like it black, then you can paint it, just like you did with screens; or change transition type to NoTransition to hide it.
Also, you should consider building your screen manager inside that kv lang string.
Old, but in case anyone runs into this issue:
To clarify the vague responses in the comments, you need to paint your screen manager the same way you would paint a screen.
Example in kvlang:
ScreenManagement:
canvas.before:
Color:
rgba: 1,1,1,1
Rectangle:
size: self.size
pos: self.pos
id: screen_manager
transition: NoTransition()
Screen1:
Screen2:
Settings_:

Categories