adding widgets in kivy - python

I am trying to construct a widget in the middle of the screen. I am not using pos_hint or size_hint because I will be altering the widget's position later but when I construct the widget, its size and position is not correct. Here is my code:
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.widget import Widget
from kivy.graphics import *
from kivy.clock import Clock
class Sprite(Widget):
def __init__(self, **kwargs):
super(Sprite, self).__init__(**kwargs)
Clock.schedule_interval(self.update_canvas, 1.0/60)
def update_canvas(self, dt):
with self.canvas:
Rectangle(size=self.size, pos=self.pos)
class RootWidget(FloatLayout):
def __init__(self, **kwargs):
super(RootWidget, self).__init__(**kwargs)
self.add_widget(Sprite(
size=(Window.width*0.1, Window.height*0.1),
center=(Window.width*0.5, Window.height*0.5)
))
class MyApp(App):
def build(self):
app = RootWidget()
return app
if __name__=="__main__":
MyApp().run()
Why is the widget's size not equal to 1/10th of the window size and why is it's center at the top right corner of the window?

The size hint defaults to a value unless you define it explicitly and it takes priority over the size property. You may not want to want to use the size hint functionality but unless you disable it, the size that you set manually will be overridden by default. Try adding the following modification and compare the result with and without the size_hint line:
self.add_widget(Sprite(
size_hint=(None,None),
size=(Window.width*0.1, Window.height*0.1),
center=(Window.width*0.3, Window.height*0.5)
))
You can see the actual dimensions set by using the inspector: python myscript.py -m inspector and hitting control+E in the kivy window
reference: http://kivy.org/docs/api-kivy.modules.inspector.html
center is an alias for (x + width/.2, y + height/.2) but the width and height default to 100. At the time of creation these default measurements are what are used for the reverse calculation to set x and y when you make your call so if your height and width are different from 100 then you'll be offset by the difference. Use x and y directly (i.e. pos=(..)) when instantiating, not center.

Related

How to fix the length of a line in kivy

As you all know kivy Line take points with (x1,y1.x2,y2,x3,y3). I want to create a geometric compass like it is in here. Considering the compass here I created a basic framework of this, which doesn't seems to be good enough.. (though isn't even close to match the compass there but is just the basic idea)
import kivy
from kivy.uix.widget import Widget
from kivy.uix.widget import Canvas
from kivy.graphics import Color
from kivy.graphics import Line
from kivy.uix.floatlayout import FloatLayout
from kivymd.app import MDApp
from kivy.uix.button import Button
from kivymd.uix.card import MDCard
class Main(FloatLayout):
def __init__(self,**kwargs):
super(Main, self).__init__(**kwargs)
self.my_widget = Widget(size_hint= (0.6,0.6),pos_hint = {'x':0.5,'top' : 0.8})
self.add_widget(self.my_widget)
with self.my_widget.canvas:
Color(rgba = (0,0,0,1))
def on_touch_downah(self,touch):
self.line = Line(points = (touch.x,touch.y),width = 4)
self.canvas.add(self.line)
def on_touch_moveah(self,touch):
self.line.points += touch.x,touch.y
self.my_widget.bind(on_touch_down=on_touch_downah)
self.my_widget.bind(on_touch_move=on_touch_moveah)
def on_touch_downeh(self, touch):
self.x_ = touch.x
self.y_ = touch.y
self.lines = Line(points=(touch.x,touch.y),width = 5)
self.canvas.add(self.lines)
def on_touch_moveeh(self, touch):
self.x2 = self.x_ + 0.1
self.y2 = self.y_ + 0.1
self.lines.points = (self.x_,self.y_,touch.x,touch.y,touch.x,touch.y)
self.my_widget.bind(on_touch_down=on_touch_downeh)
self.my_widget.bind(on_touch_move=on_touch_moveeh)
class Mysapp(MDApp):
def build(self):
return Main()
Mysapp().run()
It is just a framework but the only problem here is the length of the straight _line is not getting fixed, meaning it keep changing because of x3,y3. Is there any way to fix the length of this line so that under no circumstances its length would get modified but it should still be able to change its position?
Or is there any better widget for this purpose other than the circle widget? I want the user to be able to create curves and arcs as well as circles therefore can't switch to circle widget. The user should be able to use it just as the geometric compass as you can see the provided link..

Moving Canvas Context From Window to Widget in Kivy's RelativeLayout in Python, Alone (No .kv Files)

I've got the following code snippet that will draw a board. I've read about RelativeLayout and seen tutorials for manipulating the .kv file, but no explanation on how to relativize the coordinates to the widget with just Python code.
from kivy.uix.widget import Widget
from kivy import graphics
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.app import App
class Chessboard(Widget):
def __init__(self, **kwargs):
super(Chessboard, self).__init__(**kwargs)
self.bind(pos=self.drawBoardandPieces)
self.bind(size=self.drawBoardandPieces)
self.drawBoardandPieces()
def drawBoardandPieces(self, *args):
with self.canvas:
# Reset the canvas in case of redraws.
self.canvas.clear()
# Define the lengths of the edges of the squares.
edge_len = min(self.height, self.width) // 8
for column in range(0, 8):
for row in range(0, 8):
if ((row + column) % 2) == 0:
graphics.Color(0, 0, 1)
self.dark_rect = graphics.Rectangle(pos=(column*edge_len, row*edge_len), size=(edge_len, edge_len))
else:
graphics.Color(1, 1, 1)
self.light_rect = graphics.Rectangle(pos=(column*edge_len, row*edge_len), size=(edge_len, edge_len))
def on_touch_down(self, touch):
print(touch.pos)
class SCApp(App):
def build(self):
app_layout = BoxLayout(orientation="vertical")
app_layout.add_widget(widget=Chessboard())
play_again_btn = Button(text="Play Again", size_hint=(1, 1))
app_layout.add_widget(widget=play_again_btn)
return app_layout
SCApp().run()
Instead of what this code renders, I need the board drawn on the canvas to appear above the button in its own BoxLayout container.
Also, will click event need to be contained in the RelativeLayout container, as well, to work only in the context of the board?
Note that the documentation specifies:
Kivy drawing instructions are not automatically relative to the
widgets position or size.
So you must also handle the position of your chessboard. You can handle the position using something like:
def drawBoardandPieces(self, *args):
with self.canvas:
# Reset the canvas in case of redraws.
self.canvas.clear()
# Define the lengths of the edges of the squares.
edge_len = min(self.height, self.width) // 8
for column in range(0, 8):
for row in range(0, 8):
if ((row + column) % 2) == 0:
graphics.Color(0, 0, 1)
self.dark_rect = graphics.Rectangle(pos=(self.width/2 - 4*edge_len + column*edge_len, self.y + row*edge_len), size=(edge_len, edge_len))
else:
graphics.Color(1, 1, 1)
self.light_rect = graphics.Rectangle(pos=(self.width/2 - 4*edge_len + column*edge_len, self.y + row*edge_len), size=(edge_len, edge_len))
This uses self.y as the bottom of the chess board and calculates the x based on the width of the Widget and the edge_len. Probably should also add self.x to the x position, but that is zero in your case.
In general, your on_touch_down() method should check if the touch is on your Widget as stated in the documentation. If you determine that no other Widgets need to know about that touch, your on_touch_down() method can return True, otherwise it should return False.
I solved it. Here's the corrected code in the app. All you need to do is to place the widget into its own RelativeLayout. Then, the RelativeLayout widget can, itself, be placed inside of a box in BoxLayout, a grid position in GridLayout, etc.
from kivy.uix.relativelayout import RelativeLayout
# All of that other code.
class SCApp(App):
def build(self):
app_layout = BoxLayout(orientation="vertical")
chessboard_layout = RelativeLayout()
chessboard_layout.add_widget(widget=Chessboard())
app_layout.add_widget(widget=chessboard_layout))
play_again_btn = Button(text="Play Again", size_hint=(1, 1))
app_layout.add_widget(widget=play_again_btn)
return app_layout

Kivy update label dynamically on canvas

I am trying update Label on canvas , but it changed only if I call update() method from MyApp(APP).
And what is interesting that method obj Slider (on touch) works perfectly
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.core.window import Window
from kivy.uix.label import Label
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.graphics import (Color, Ellipse)
from kivy.uix.slider import Slider
import random
from functools import partial
class Canvas_w(Widget):
def __init__(self, **kwargs):
self.i = 0
super(Canvas_w, self).__init__(**kwargs)
self.label_text =Label(text= "0",color = (255,0,0))
def update_text(self, text):
self.label_text.text = text
def on_touch_down(self, touch):
with self.canvas:
d=30
self.i+=1
Color(1., 0, 0)
Ellipse(pos=(touch.x - d/2, touch.y - d/2), size=(20,20))
self.label_text=str(self.i)
class Label_l(GridLayout):
bones = 0
def __init__(self, **kwargs):
super(Label_l, self).__init__(**kwargs)
self.label_text =Label(text= "0")
self.add_widget(self.label_text)
#self.label_text.text=self.C.i
def update_text(self, text):
Label_l.bones=text
self.label_text.text = str(Label_l().bones)#
Label_l.text=str(Label_l().bones)
class WidgetContainer(GridLayout):
sld_value = 0.00001
def __init__(self, **kwargs):
super(WidgetContainer, self).__init__(**kwargs)
self.cols = 1
self.speedControl = Slider(min=0.00001, max=3, step=0.0001)
self.add_widget(Label(text="Speed Iteration"))
self.add_widget(self.speedControl)
self.add_widget(Label(text=''))
self.speedValue = Label(text="0.00001")
self.add_widget(self.speedValue)
self.speedControl.bind(value=self.on_value)
def on_value(self, instance, speed):
self.sld_value = speed
WidgetContainer.sld_value = self.sld_value
self.speedValue.text = str(round(speed,5))
class MyApp(App):
def build(self):
b3 = BoxLayout(orientation="horizontal")
b1 = FloatLayout()
self.painter = Canvas_w()
self.label_l= Label_l()
b1.add_widget(self.label_l)
b1.add_widget(self.painter)
b1.add_widget(WidgetContainer())
b3.add_widget(b1)
return(b3)
if __name__=="__main__":
MyApp().run()
I am trying update Label on canvas , but it changed only if I call update() method from MyApp(APP).
And what is interesting that method obj Slider (on touch) works perfectly
A few problems with your code:
The color attribute of a Label requires a list of four values with each value in the range 0 to 1. Change self.label_text =Label(text= "0",color = (255,0,0)) to self.label_text =Label(text= "0",color = (1,0,0,1)) in the Canvas_w class.
The above Label is never getting drawn. You need to add self.add_widget(self.label_text) to the __init__() method of Canvas_w.
You have another Label in the Label_l class that is being drawn on top of the Label in the Canvas_w.
After fixing the above problems, I believe your code will work as you expect.
If you call self.canvas.clear(), it appears as though the label_text child of Canvas_w is removed. Actually it is not removed, but is just not visible (perhaps overdrawn). One way to make it visible is to just make sure it is drawn last by removing it and then re-adding it (last added child is drawn last), like this:
self.canvas.clear()
self.remove_widget(self.label_text)
self.add_widget(self.label_text)
After a bit more research, I believe that the canvas.clear() is removing the drawing of the Label from the Canvas. You can, however, do your drawing on canvas.before by using:
with self.canvas.before:
in the on_touch_down() method. And rather than using:
self.canvas.clear()
use:
self.canvas.before.clear()
I believe that your Label will be displayed correctly after doing the above.

Kivy. Why size_hint doesn't work at widget?

I want to scale an image in Widget using size_hint, but it doesn't work. What did I do wrong?
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.image import Image
class Game(Widget):
def __init__(self):
super().__init__(size=[1280, 960])
self.add_widget(Image(source='img.jpg', size_hint=(1, 1)))
class GameApp(App):
def build(self):
gm = Game()
return gm
GameApp().run()
As a result I see a black window with a 100x100 image.
The problem is that you are adding your Image to a class that extends Widget. The Widget class is not intended as a container, and does not handle things like size_hint. So, I suggest changing Game to extend some type of container, like FloatLayout. Here is your code with that change:
class Game(FloatLayout):
def __init__(self):
super().__init__(size=[1280, 960])
self.add_widget(Image(source='img.jpg', size_hint=(1,1), allow_stretch=True))
You may need the allow_stretch=True to allow the Image to stretch the source.
Question
Why size_hint doesn't work for Widget?
Answer
The keyword, size_hint that you have provided, is not applying to the
Widget. It is applying to the Image object. The base class of Image widget is a Widget. The default size of a widget is (100, 100) and default size_hint is (1, 1). Therefore, by providing size_hint=(1, 1) when instantiating an Image widget, you are not changing the default size_hint or size.
When you provided size=[1280, 960] to the super constructor, you have already overrode the default size of the widget of (100, 100) or default size_hint of (1, 1)
Solution
If you add pos=self.pos, size=self.size, allow_stretch=True) to Image, you will get an image displayed.
But there is still a black strip at the bottom.
Snippets
self.add_widget(Image(source='Jupiter.png', pos=self.pos, size=self.size, allow_stretch=True))
Recommended Solution.
You can replace Image object by doing the following:
Use the Widget's canvas to display an image in a Rectangle
Bind the Widget's pos and size to a method to redraw the image
Add import statement, from kivy.graphics import Rectangle
Snippets
from kivy.graphics import Rectangle
class Game(Widget):
def __init__(self):
super().__init__(size=[1280, 960])
with self.canvas:
self.rect = Rectangle(source="Jupiter.png", pos=self.pos, size=self.size)
self.bind(pos=self.redraw, size=self.redraw)
def redraw(self, *args):
self.rect.size = self.size
self.rect.pos = self.pos
Output

Issues with Scatter size and position

I encountered some issue related to scatter object.
From my codes below. After I resize a Scatter (self.size_hint_x, self.size_hint_y = 0.3, 0.3), the objects (canvas, label) inside the Scatter is not resized as well. I did tried to apply size_hint=1 to the Canvas and Label inside the Scatter, however the result still the same.
Another issue I encountered is the retrieving of the X, Y position (relative to the parent) for the Canvas/Label in a Scatter. It always give me (0,0).
My Code
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.widget import Widget
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.label import Label
from kivy.uix.scatter import Scatter
from kivy.graphics import Color, Rectangle, Canvas
class Avatar(Scatter):
def __init__(self, **kwargs):
super(Avatar, self).__init__(size_hint=(None,None), **kwargs)
with self.canvas:
Color(0, 0, 0)
Rectangle(pos=(self.x, self.y), size=(self.width, self.height))
self.lbl = Label(text='Test', size_hint_x=1, size_hint_y=1)
self.add_widget(self.lbl)
# Scatter size is 30% of the GameBackground
# ISSUE: After resize my Scatter, the objects inside is not resized as well.
self.size_hint_x, self.size_hint_y = 0.3, 0.3
class GameBackground(FloatLayout):
def __init__(self, **kwargs):
super(GameBackground, self).__init__(**kwargs)
with self.canvas:
Color(1, 0, 1)
Rectangle(pos = (0, 0), size = (Window.width,Window.height))
self.elf = Avatar()
self.add_widget(self.elf)
self.elf.x = 200
self.elf.y = 300
# Get the X, Y position of the Scatter and the label inside the Scatter relative to the parent.
print self.elf.pos #<-- This works.
print self.elf.lbl.pos #<-- ISSUE: This not working.
class GameApp(App):
def build(self):
return GameBackground()
if __name__ == '__main__':
GameApp().run()
Did I miss something? Thanks for any advise.
I'm new to Kivy. So pardon me if my qns is dumb. :P
Did you read the documentation for Scatter at all. It says that
...specific behavior makes the scatter unique, and there are some advantages / constraints that you should consider:
The children are positioned relative to 0, 0. The scatter position has no impact of the position of children.
This applies to the size too. If you want to resize the scatter, use scale, not size. (read #1.)
That answer your first question. It says use scale, not size. There is also the the method apply_transform that you may find useful for other transformations. I have never tried this method though but I cannot see the translate value (I can see Rotate and Scale)
Regarding to your second question. You are adding a Rectangle in the self.x and self.y position that is (0,0). So, your Rectangle is in that position. If you drag (using your fingers or mouse) your widget. The position of the Rectangle remains relative to the Scatter. So, unless you change the position of the Rectangle (with code) it will be always be in the (0,0). The transformations are always relative to the Scatter.
This question might be related and explains a few issues with not using the Kivy Language to add Vertex Instructions (i.e. Rectangles). You should consider it because what you are doing seems to be related.
* EDIT - just the necessarily corrections according to my understanding of what you are trying to achieve *
1) Don't use size hints like the ones you are using.
1.1) Instead of:
self.lbl = Label(text='Test', size_hint_x=1, size_hint_y=1)
use:
self.lbl = Label(text='Test', width=self.width, height=self.height)
1.2) And, instead of:
self.size_hint_x, self.size_hint_y = 0.3, 0.3
use:
self.scale = 0.3
2) The position is relative to the scatter. Therefore you need to transale the coordinates to the parent.
2.1) Instead of:
print self.elf.lbl.pos #<-- ISSUE: This not working.
use:
print self.elf.to_parent(*self.elf.lbl.pos)
Here is the complete code:
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.widget import Widget
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.label import Label
from kivy.uix.scatter import Scatter
from kivy.graphics import Color, Rectangle, Canvas
class Avatar(Scatter):
def __init__(self, **kwargs):
super(Avatar, self).__init__(size_hint=(None,None), **kwargs)
with self.canvas:
Color(0, 0, 0)
Rectangle(pos=(self.x, self.y), size=(self.width, self.height))
#self.lbl = Label(text='Test', size_hint_x=1, size_hint_y=1)
self.lbl = Label(text='Test', width=self.width, height=self.height)
self.add_widget(self.lbl)
# Scatter size is 30% of the GameBackground
# ISSUE: After resize my Scatter, the objects inside is not resized as well.
# self.size_hint_x, self.size_hint_y = 0.3, 0.3
self.scale = 0.3
class GameBackground(FloatLayout):
def __init__(self, **kwargs):
super(GameBackground, self).__init__(**kwargs)
with self.canvas:
Color(0, 0, 1)
Rectangle(pos = (0, 0), size = (Window.width,Window.height))
self.elf = Avatar()
self.add_widget(self.elf)
self.elf.x = 200
self.elf.y = 300
# Get the X, Y position of the Scatter and the label inside the Scatter relative to the parent.
print self.elf.pos #<-- This works.
print self.elf.lbl.pos #<-- ISSUE: This not working.
print self.elf.to_parent(*self.elf.lbl.pos)
class GameApp(App):
def build(self):
return GameBackground()
if __name__ == '__main__':
GameApp().run()

Categories