As it can be understood from the code, I want to assign a new position to the ball on the screen when a certain pixel range is clicked, that is, I want to change its current position. I tried this with self.ids.widget.pos and things like that but didn't get any results.
There is also one more thing I want to ask regardless of the subject. While I define this ball on the screen, the ball does not appear in the center of the screen, I adjusted it myself. Normally I use a 300 width and 500 height screen for the application, but I was able to achieve this by giving 175 width and 275 height values to get this ball right in the middle. How can I better adjust this?
I have removed the parts I don't need to shorten the code, but I can add them later if you want.
from kivy.lang import Builder
from kivy.core.window import Window
from kivy.uix.screenmanager import Screen
from kivymd.app import MDApp
from time import sleep, time
from random import randint
from kivy.properties import NumericProperty
from kivy.uix.widget import Widget
from kivy.clock import Clock
from kivy.graphics import Color
Window.size = (300, 500)
helper = """
ScreenManager:
MenuScreen:
ReflexScreen:
MainScreen:
AimScreen:
<Ball>:
canvas:
Ellipse:
pos: self.pos
size: 50,50
<AimWidget>
ball: pong_ball
Ball:
id: pong_ball
center_x: 175
center_y: 275
<AimScreen>
id: aim
name: 'aim'
AimWidget:
id: widget
"""
class Ball(Widget):
pos_x = NumericProperty()
pos_y = NumericProperty()
class AimWidget(Widget):
def update(self, dt):
pass
class AimScreen(Screen):
aim = AimWidget()
Clock.schedule_interval(aim.update, 1.0/60.0)
def on_pre_enter(self, *args):
pass
def on_touch_down(self, touch):
if (touch.x >= 125 and touch.x <= 175) and (touch.y >= 225 and touch.y <= 275):
pass
Here is a modified version of your code:
from kivy.app import App
from kivy.lang import Builder
from kivy.core.window import Window
from kivy.uix.screenmanager import Screen
from kivy.uix.widget import Widget
from kivy.clock import Clock
Window.size = (300, 500)
helper = """
ScreenManager:
# MenuScreen:
# ReflexScreen:
# MainScreen:
AimScreen:
<Ball>:
size_hint: None, None
size: 50, 50
canvas:
Ellipse:
pos: self.pos
size: self.size
<AimWidget>
ball: pong_ball
Ball:
id: pong_ball
center: root.center
<AimScreen>
id: aim
name: 'aim'
AimWidget:
id: widget
"""
class Ball(Widget):
pass
class AimWidget(Widget):
def update(self, dt):
pass
class AimScreen(Screen):
aim = AimWidget()
Clock.schedule_interval(aim.update, 1.0 / 60.0)
def on_pre_enter(self, *args):
pass
def on_touch_down(self, touch):
if (touch.x >= 125 and touch.x <= 175) and (touch.y >= 225 and touch.y <= 275):
ball = self.ids.widget.ball
ball.center = touch.pos
The Ball widget size was the default (100, 100), so the canvas instructions were drawing the circle in the lower left corner of the Ball widget. Added size to make it (50,50), the desired size of the circle.
The canvas instructions for the Ball, now just reference the size and pos of the Ball widget.
The Ball in the AimWidget is initially positioned using center. This works because the Ball is now the same size as the Ellipse.
The on_touch_down() method can now just set the center of the Ball to the touch.pos.
Related
I'm developing a kivy app, for desktop, i separated some goals to archive, but struggling in the fist one, i need to drawn a image as i move the mouse when it's pressed, but right now, only show a image when is touch down event and at end of the move event
here is my code
import kivy
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.label import Label
from kivy.uix.image import Image
from kivy.graphics import *
kv = """
<MainScreen>:
FloatLayout:
# on_touch_move: print_cordinates(root.touch)
# on_touch_move: add_images(root.touch)
on_touch_down: root.on_touch_down_2(args[1])
on_touch_move: root.on_touch_move_2(args[1])
<Image>:
size: self.texture_size
"""
brush_size = 10
class MainScreen(FloatLayout):
def on_touch_down_2(self, touch):
self.add_widget(Image(source='img/brush/square.png',
pos=(touch.x - 10/2, touch.y - 10/2)))
def on_touch_move_2(self, touch):
self.add_widget(Image(source='img/brush/square.png',
pos=(touch.x - 10/2, touch.y - 10/2)))
class MainApp(App):
def build(self):
return MainScreen()
MainApp().run()
the image i use is simple 10px x 10px green square(the color don't matter now)
I was under the impression that svg worked like a canvas instruction.
However, I am unable to get the svg to change color. Part of the code below is using the code provided in the sample at Kivy's github.
with self.canvas:
Color(0.3833, 1.0, 0.0)
Entire Code
import sys
from glob import glob
from kivy.uix.scatter import Scatter
from kivy.app import App
from kivy.graphics.svg import Svg
from kivy.core.window import Window
from kivy.uix.floatlayout import FloatLayout
from kivy.lang import Builder
from random import randint
from kivy.graphics import *
Builder.load_string("""
<SvgWidget>:
do_rotation: False
<FloatLayout>:
id: svg_holder
canvas.before:
Color:
rgb: (1, 1, 1)
Rectangle:
pos: self.pos
size: self.size
""")
class SvgWidget(Scatter):
def __init__(self, filename, **kwargs):
super(SvgWidget, self).__init__(**kwargs)
with self.canvas:
Color(0.3833, 1.0, 0.0)
svg = Svg(filename)
self.size = svg.width, svg.height
def remove(self):
print('remove', self)
class SvgApp(App):
def build(self):
self.root = FloatLayout()
for i in range(6):
svg = SvgWidget('star.svg', size_hint=(None, None))
self.root.add_widget(svg)
svg.scale = randint(1,4)
svg.center = randint(0,Window.width), randint(0,Window.height)
if __name__ == '__main__':
SvgApp().run()
I don't think what you asking for is possible since the Svg contains Color instructions itself, so it overrides yours... Maybe your code can use an overlay with opacity to make the desired effect
What I try is part of a larger App but I posted here the minimum to understand my problem:
I want to change the size of a rectangle, and that this size depends on the root.height .
3 cases:
1) When I create a rectangle with "root.height/4" as a height, the size varies as I resize the root window. So far no problem
2) When I tried to assign "root.height/4" to an attribut and call this attribut when setting the size of my rectangle I get the following error:
"TypeError: a float is required".
Python file:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import Rectangle, Color
class BriqueApp(App):
def build(self):
return BriqueGUI()
class BriqueGUI(Widget):
pass
BriqueApp().run()
Kv file:
#:kivy 1.9.1
<BriqueGUI>:
h: root.height/4
color: (1,0,0,1)
canvas:
Color:
rgba: root.color
Rectangle:
size: (200, self.h)
pos: (0,0)
3) When I tried to reassign a value depending on, for example "root.height/2", when an event is call (here on_touch_down) : The size of my Rectangle changes but when I resize my windows my rectangle doesn't resize with it.
You can see the problem here loading the file, clicking in the main window, and changing its size.
Python file:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import Rectangle, Color
class BriqueApp(App):
def build(self):
return BriqueGUI()
class BriqueGUI(Widget):
def on_touch_down(self, touch):
self.h = self.height/2
BriqueApp().run()
Kv file:
#:kivy 1.9.1
<BriqueGUI>:
h: 20
color: (1,0,0,1)
canvas:
Color:
rgba: root.color
Rectangle:
size: (200, self.h)
pos: (0,0)
2) You have to create property in your Python code as well:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import Rectangle, Color
from kivy.properties import NumericProperty
from kivy.lang import Builder
Builder.load_string("""
<BriqueGUI>:
h: root.height/4
color: (1,0,0,1)
canvas:
Color:
rgba: root.color
Rectangle:
size: (200, self.h)
pos: (0,0)
""")
class BriqueApp(App):
def build(self):
return BriqueGUI()
class BriqueGUI(Widget):
h = NumericProperty(0.0)
BriqueApp().run()
3) You're only assigning once a value to your h property. What you need is bind method that allows you to associate a callback that will be called any time the property changes:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import Rectangle, Color
from kivy.properties import NumericProperty
from kivy.lang import Builder
Builder.load_string("""
<BriqueGUI>:
h: 20
color: (1,0,0,1)
canvas:
Color:
rgba: root.color
Rectangle:
size: (200, self.h)
pos: (0,0)
""")
class BriqueApp(App):
def build(self):
return BriqueGUI()
class BriqueGUI(Widget):
def on_touch_down(self, touch):
self.h = self.height/2
self.bind(height=self.set_h)
def set_h(self, instance, value):
self.h = value/2
BriqueApp().run()
I have BoxLayout with 3 elements, I need that first and last elements to occupy minimum available space. Middle element has fixed proportion (1:1), so when I resize the window, side elements become too small and content go out of it. I need e.g. label's (or button's, or even set's of different elements) text to be always inside the label. This size shouldn't be more, so I can say it should be fixed size depending on its content.
UPDATE:
I've made a mistake, size can be more, but not less. How it should be then?
UPDATE:
So it's my BoxLayout:
When I expand the window, only side parts should expand:
And when I constrict the window, side parts should have some minimum size:
So it's kind of fixed minimum I think.
Content size of Label is avaiable through texture_size property, so you can set size_hint to None and bind size to content size:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.lang import Builder
kv = '''
<MyButton>:
size_hint: None, 1
size: self.texture_size
'''
Builder.load_string(kv)
class MyButton(Button):
pass
class MyWidget(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.add_widget(MyButton(text="Fixed size button"))
self.add_widget(Button(text="Normal button"))
self.add_widget(MyButton(text="Fixed size button"))
class MyApp(App):
def build(self):
return MyWidget()
if __name__ == '__main__':
MyApp().run()
You should also check text_size property. From documentation: "By default, the label is not constrained to any bounding box. You can set the size constraint of the label with this property. The text will autoflow into the constrains. So although the font size will not be reduced, the text will be arranged to fit into the box as best as possible, with any text still outside the box clipped". For example:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.lang import Builder
kv = '''
<MyButton>:
text_size: self.size
valign: "middle"
halign: "center"
'''
Builder.load_string(kv)
class MyButton(Button):
pass
class MyWidget(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.add_widget(MyButton(text="Example text which is too long to fit in one line"))
self.add_widget(Button(text="Normal button"))
self.add_widget(MyButton(text="Example text which is too long to fit in one line"))
class MyApp(App):
def build(self):
return MyWidget()
if __name__ == '__main__':
MyApp().run()
UPDATE
If you want even more control about the way widgets scale, you can create method which calculate values for widgets and have it binded to changing size property (either bind or implementing on_size()). For example:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.widget import Widget
from kivy.uix.label import Label
from kivy.lang import Builder
from functools import partial
kv = '''
<CentralWidget>:
pos_hint: {'center_y': .5}
size_hint: None, None
canvas:
Color:
rgb: 1, 0, 1
Rectangle:
pos: self.pos
size: self.size
<SideWidget>:
pos_hint: {'center_y': .5}
size_hint: None, 1
canvas.before:
Color:
rgb: 0, 0, 1
Rectangle:
pos: self.pos
size: self.size
'''
Builder.load_string(kv)
class CentralWidget(Widget):
pass
class SideWidget(Label):
pass
class MyWidget(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
w1 = SideWidget(text="............................")
w2 = CentralWidget()
w3 = SideWidget(text="............................")
self.add_widget(w1)
self.add_widget(w2)
self.add_widget(w3)
def on_size(self, *args):
# self.size - size of parent widget
# self.children - children of widget
# self.children[0].texture_size - sife of content of selectend children widget
# self.children[0].size - size of selected children widget to set
if((self.size[0]-500)/2 > self.children[0].texture_size[0]):
self.children[0].size = ((self.size[0]-500)/2, 0)
self.children[1].size = (500, 500)
self.children[2].size = ((self.size[0]-500)/2, 0)
else:
self.children[1].size = (self.size[0]-2*self.children[0].texture_size[0], self.size[0]-2*self.children[0].texture_size[0])
class MyApp(App):
def build(self):
return MyWidget()
if __name__ == '__main__':
MyApp().run()
I have a ver long text in kivy. I want adjustment dynamic height depend of qty of text.
My code is this.
import kivy
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
class DynamicHeight(App):y
def build(self):
grid = gl = GridLayout(cols=1)
for i in range(3):
l = Label(text='Text a longer line line line line line line line line', halign='left',text_size=(300, None))
grid.add_widget(l)
return grid
DynamicHeight().run()
I want that height of label or height row of gridlayout adjustment according to the amount of text.
Although there are solutions proposed already, i feel they don't leverage the kivy way of doing things, and that this way is cleaner. What you need is to bind the text_size to the available width, and bind the height of the widget to the rendered texture size.
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.floatlayout import FloatLayout
class MyApp(App):
def build(self):
root = FloatLayout()
b = GridLayout(
cols=1,
pos_hint={
'center_x': .5,
'center_y': .5},
size_hint=(None, None),
spacing=20,
width=200)
b.bind(minimum_height=b.setter('height'))
root.add_widget(b)
for text_length in range(0, 80, 20):
l = Label(
text='word ' * text_length,
size_hint_y=None)
l.bind(width=lambda s, w:
s.setter('text_size')(s, (w, None)))
l.bind(texture_size=l.setter('size'))
b.add_widget(l)
return root
if __name__ == '__main__':
MyApp().run()
I found the solution, with help of thopiekar.
For those needing this. So far I have not found kivy do without this method
import kivy
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
class MultiLineLabel(Button):
def __init__(self, **kwargs):
super(MultiLineLabel, self).__init__( **kwargs)
self.text_size = self.size
self.bind(size= self.on_size)
self.bind(text= self.on_text_changed)
self.size_hint_y = None # Not needed here
def on_size(self, widget, size):
self.text_size = size[0], None
self.texture_update()
if self.size_hint_y == None and self.size_hint_x != None:
self.height = max(self.texture_size[1], self.line_height)
elif self.size_hint_x == None and self.size_hint_y != None:
self.width = self.texture_size[0]
def on_text_changed(self, widget, text):
self.on_size(self, self.size)
class DynamicHeight(App):
def build(self):
grid = GridLayout(cols=1,size_hint_x=None, width="300dp")
l=['This Text very long, should add multiple lines, automatically. This Text very long, should add multiple lines, automatically', 'One line']
for i in l:
l = MultiLineLabel(text=i)
grid.add_widget(l)
return grid
DynamicHeight().run()
And works perfectly!!!!!
text.size() method isn't changing height atribute of Label. If text is too long, it will overlap with content below:
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
class DynamicHeight(App):
def build(self):
layout = GridLayout(cols=1, spacing=20)
l = Label(text='! '*100, text_size=(10, None), size_hint_y=None, height=10)
b = Button(text='...', size_hint_y=None)
layout.add_widget(l)
layout.add_widget(b)
return layout
DynamicHeight().run()
You need to calculate the heigh of the text and set it with height attribute manually. I don't know any nice and clean way to do this. Here is a dirty way:
before = label._label.render()
label.text_size=(300, None)
after = label._label.render()
label.height = (after[1]/before[1])*before[1] # ammount of rows * single row height
Example:
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.scrollview import ScrollView
class DynamicHeight(App):
def build(self):
layout = GridLayout(cols=1, size_hint_y=None, spacing=20)
layout.bind(minimum_height=layout.setter('height'))
for i in xrange(1, 20):
l = Label(text='Text ' * (i*10), text_size=(300, None), size_hint_y=None)
# calculating height here
before = l._label.render()
l.text_size=(300, None)
after = l._label.render()
l.height = (after[1]/before[1])*before[1] # ammount of rows * single row height
# end
layout.add_widget(l)
root = ScrollView()
root.add_widget(layout)
return root
DynamicHeight().run()
Following up on tshirtman's answer, I created code for doing the same thing in kv-lang. It might be a bit clearer what's going on, since you don't need to analyse the callback functions.
What's happening is that the label's width gets set in accordance with the layout, while the height gets set to the texture size of the text. So as the text string gets longer, the texture can only grow in height, which we can apply above, by setting the height of the boxlayout to my_label.height.
# -*- coding:utf8 -*-
from kivy.lang import Builder
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import StringProperty
from kivy.core.window import Window
Window.size = (400, 700)
PLACEHOLDER_TEXT = u'''The bindings illustrated in tshirtman's example are created automatically when using kv-lang.
Notice how the "id: my_label" allows us to access the Label's attributes in the BoxLayout's height declaration.'''
kv_string = """
<Example>:
orientation: 'vertical'
BoxLayout:
# colored background for affected area:
canvas.before:
Color:
rgba: 0.3, .4, .4, .6
Rectangle:
pos: self.pos
size: self.size
size_hint_y: None
height: my_label.height
Label:
id: my_label
text: root.text
font_size: '14dp'
text_size: (self.width, None)
size: self.texture_size
valign: 'top'
halign: 'left'
BoxLayout:
orientation: 'vertical'
size_hint_y: 1
canvas.before:
Color:
rgba: 0.9, .0, .5, .6
Rectangle:
pos: self.pos
size: self.size
TextInput:
text: root.PLACEHOLDER_TEXT
on_text: root.text = self.text
text_size: self.size
auto_indent: True
Label:
size_hint_y: None
height: '50dp'
text: 'String length: ' + str(len(root.text))
"""
Builder.load_string( kv_string )
class Example (BoxLayout ):
PLACEHOLDER_TEXT = PLACEHOLDER_TEXT
text = StringProperty( )
def __init__(self, **kwargs):
super( Example, self).__init__(**kwargs)
class MyApp(App):
def build(self):
root = Example()
return root
MyApp().run()