Rotate kivy widget - python

I have a layout scheme more or less like this:
#at build:
float_layout_1 = FloatLayout()
box_layout_1 = BoxLayout()
box_layout_2 = BoxLayout()
float_layout_2 = FloatLayout()
self.image = Image()
float_layout_2.add_widget(self.image)
box_layout_2.add_widget(float_layout_2)
box_layout_1.add_widget(box_layout_2)
float_layout_1.add_widget(box_layout_1)
return float_layout_1
I want to rotate just the Image widget, when I call a function do rotate the image, everything rotates:
#at a internal class method
with self.image.canvas:
Rotate(angle, a, b, c)

Add another rotation after your widget that rotates by the inverse amount, or enclose the widget canvas in PushMatrix (before) and PopMatrix (after) instructions.

Related

How do I make my custom widget appear in my splitter using Kivy?

I'm still learning Kivy nuances, & I just can't seem to make my Chessboard widget appear in my splitter. I substituted a working button widget in the splitter for my custom widget, but it's not acting the same. What am I doing wrong?
import kivy
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.splitter import Splitter
from kivy.graphics import Color, Rectangle
from kivy.uix.image import Image
from kivy.uix.widget import Widget
kivy.require('2.0.0')
class ChessBoardWidget(Widget): # FloatLayout
def __init__(self, **kwargs):
super(ChessBoardWidget, self).__init__(**kwargs)
with self.canvas.before:
Color(0, 1, 0, 1)
self.rect = Rectangle(size=(self.width, self.height), pos=self.pos)
self.add_widget(
Image(source="./data/images/chess-pieces/DarkerGreenGreyChessBoard.png", pos=self.pos,
size_hint=(1, 1), keep_ratio=True, allow_stretch=True))
class SplitterGui(BoxLayout):
def __init__(self, **kwargs):
super(SplitterGui, self).__init__(**kwargs)
self.orientation = 'horizontal'
split1_boxlayout = BoxLayout(orientation='vertical')
split1 = Splitter(sizable_from='bottom', min_size=100, max_size=1000)
chessboard_widget = ChessBoardWidget() # was s1_button = Button(text='s1', size_hint=(1, 1)) WORKED!
s3_button = Button(text='s3', size_hint=(1, 1))
split1.add_widget(chessboard_widget) # was split1.add_widget(s1_button) WORKED!
split1_boxlayout.add_widget(split1)
split1_boxlayout.add_widget(s3_button)
self.add_widget(split1_boxlayout)
split2 = Splitter(sizable_from='left', min_size=100, max_size=1000)
s2_button = Button(text='s2', size_hint=(.1, 1))
split2.add_widget(s2_button)
self.add_widget(split2)
class SplitterTestApp(App):
def build(self):
return SplitterGui() # root
if __name__ == '__main__':
SplitterTestApp().run()
Your ChessBoardWidget is being drawn, it's just not where you expect it. Three things to keep in mind:
Using the Widget class as a container for other widgets eliminates the capabilities of widgets that are intended for use as containers such as size_hint and pos_hint. From the documentation:
A Widget is not a Layout: it will not change the position or the size
of its children. If you want control over positioning or sizing, use a
Layout.
When you reference pos or size of a widget in its __init__() method, you will always get the default values of (0,0) for pos and (100,100) for size.
References to properties (such as pos and size) in a widgets __init__() method use the current values of those properties (see #2 above), and there is no binding to handle changes in those values. So, for example, creating a Rectangle in a widgets __init__() using the widgets pos and size will create an unchanging Rectangle at pos of (0,0) and size of (100,100).
So, a fix for getting the ChessBoardWidget drawn where you expect is to just change the base class of ChessBoardWidget to RelativeLayout. The nice thing about RelativeLayout is that adding a child with the default pos and size_hint will result in the child (in your case, the Image) being drawn with the same position and size as its parent (the ChessBoardWidget). Something like this:
class ChessBoardWidget(RelativeLayout):
def __init__(self, **kwargs):
super(ChessBoardWidget, self).__init__(**kwargs)
with self.canvas.before:
Color(0, 1, 0, 1)
self.rect = Rectangle(size=(self.width, self.height), pos=self.pos)
self.add_widget(
Image(source="./data/images/chess-pieces/DarkerGreenGreyChessBoard.png", keep_ratio=True, allow_stretch=True))
Note that the green Rectangle will still be drawn at the lower left corner of the ChessBoardWidget with size of (100,100). To fix that, you either need to define the Rectangle in a kivy language file or string that gets loaded before the ChessBoardWidget gets created. Or you need to set up bindings that will redraw the Rectangle whenever the ChessBoardWidget gets re-sized.
I believe this is the easiest way to draw the green background, using Builder.load_string():
Builder.load_string('''
<ChessBoardWidget>:
canvas.before:
Color:
rgba: 0,1,0,1
Rectangle:
pos: 0,0
size: self.size
''')
class ChessBoardWidget(RelativeLayout):
def __init__(self, **kwargs):
super(ChessBoardWidget, self).__init__(**kwargs)
# with self.canvas.before:
# Color(0, 1, 0, 1)
# self.rect = Rectangle(size=(self.width, self.height), pos=self.pos)
self.add_widget(
Image(source="./data/images/chess-pieces/DarkerGreenGreyChessBoard.png", keep_ratio=True, allow_stretch=True))

kivy cut Text out of Shape

as the canvas instructions Stencil and Scissor (which seem to mo me have the same effect) are used to create masks and draw on top of it, im wondering have one could achieve this effect:
Drawing any shape on top of a background image (so far so easy). Then cut text out of the shape in order to look through on the background.
Is there any solution for that? Or any kivy/openGL instructions i have missed?
Here is something that comes close to what you want. It is a brute force method that makes the text of a kivy.core.text.Label transparent and then uses that texture in a canvas Rectangle:
from kivy.app import App
from kivy.clock import Clock
from kivy.graphics.context_instructions import Color
from kivy.graphics.texture import Texture
from kivy.graphics.vertex_instructions import Rectangle
from kivy.lang import Builder
from kivy.uix.floatlayout import FloatLayout
from kivy.core.text import Label as CoreLabel
class MyWidget(FloatLayout):
pass
Builder.load_string('''
<MyWidget>:
Image:
source: 'tester.png'
allow_stretch: True
keep_ratio: True
''')
class TestApp(App):
def build(self):
Clock.schedule_once(self.doit)
return MyWidget()
def doit(self, dt):
# build a Rectangle half the size and centered on the root (MyWidget)
size = (self.root.width/2, self.root.height/2)
pos = (self.root.x + (self.root.width - size[0])/2, self.root.y + (self.root.height - size[1])/2)
with self.root.canvas.after:
Color(1,1,1,1)
self.rect = Rectangle(pos=pos, size=size)
# create a Texture with desired text and desired size
label = CoreLabel(text='Hello', font_name='Roboto-Bold', font_size=300, valign='center', halign='center',
size=self.root.size, color=(1,1,1,1))
label.refresh()
# make the actual text transparent, and assign the new Texture to the Rectangle
self.rect.texture = self.make_text_transparent(label.texture, (255, 0, 0, 255))
def make_text_transparent(self, texture, bg_color):
# bg_color is a 4 tuple of byte values in the range 0-255
# The normal Label texture is transparent except for the text itself
# This code changes the texture to make the text transparent, and everything else
# gets set to bg_color
pixels = list(texture.pixels)
for index in range(3, len(pixels)-4, 4):
if pixels[index] == 0:
# change transparent pixels to the bg_color
pixels[index-3:index+1] = bg_color
else:
# make the text itself transparent
pixels[index] = 0
# create a new texture, blit the new pixels, and return the new texture
new_texture = Texture.create(size=texture.size, colorfmt='rgba')
new_texture.blit_buffer(bytes(pixels), colorfmt='rgba', bufferfmt='ubyte')
new_texture.flip_vertical()
return new_texture
TestApp().run()
You could generalize this by using another Texture, instead of bg_color and setting the transparent Label pixels to the corresponding pixel from the supplied Texture.

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

Why is 'root' keyword treated differently when it is called in Kivy?

How to make circular progress bar in kivy?
I have found a source above for circular progress bar and noted something weird.
from kivy.app import App
from kivy.uix.progressbar import ProgressBar
from kivy.core.text import Label as CoreLabel
from kivy.lang.builder import Builder
from kivy.graphics import Color, Ellipse, Rectangle
from kivy.clock import Clock
class CircularProgressBar(ProgressBar):
def __init__(self, **kwargs):
super(CircularProgressBar, self).__init__(**kwargs)
# Set constant for the bar thickness
self.thickness = 40
# Create a direct text representation
self.label = CoreLabel(text="0%", font_size=self.thickness)
# Initialise the texture_size variable
self.texture_size = None
# Refresh the text
self.refresh_text()
# Redraw on innit
self.draw()
def draw(self):
with self.canvas:
# Empty canvas instructions
self.canvas.clear()
# Draw no-progress circle
Color(0.26, 0.26, 0.26)
Ellipse(pos=self.pos, size=self.size)
# Draw progress circle, small hack if there is no progress (angle_end = 0 results in full progress)
Color(1, 0, 0)
Ellipse(pos=self.pos, size=self.size,
angle_end=(0.001 if self.value_normalized == 0 else self.value_normalized*360))
# Draw the inner circle (colour should be equal to the background)
Color(0, 0, 0)
Ellipse(pos=(self.pos[0] + self.thickness / 2, self.pos[1] + self.thickness / 2),
size=(self.size[0] - self.thickness, self.size[1] - self.thickness))
# Center and draw the progress text
Color(1, 1, 1, 1)
Rectangle(texture=self.label.texture, size=self.texture_size,
pos=(self.size[0]/2 - self.texture_size[0]/2, self.size[1]/2 - self.texture_size[1]/2))
def refresh_text(self):
# Render the label
self.label.refresh()
# Set the texture size each refresh
self.texture_size = list(self.label.texture.size)
def set_value(self, value):
# Update the progress bar value
self.value = value
# Update textual value and refresh the texture
self.label.text = str(int(self.value_normalized*100)) + "%"
self.refresh_text()
# Draw all the elements
self.draw()
class Main(App):
def just_function(self):
print(self.root) # <----- this will print None
# Simple animation to show the circular progress bar in action
def animate(self, dt):
print(self.root) # <---- this prints CircularProgressBar object
if self.root.value < 80:
self.root.set_value(self.root.value + 1)
else:
self.root.set_value(0)
# Simple layout for easy example
def build(self):
container = Builder.load_string(
'''CircularProgressBar:
size_hint: (None, None)
height: 200
width: 200
max: 80''')
# Animate the progress bar
Clock.schedule_interval(self.animate, 0.1)
print(self.root) # <---- this prints None
self.just_function() # <---- this prints None
return container
if __name__ == '__main__':
Main().run()
When you take a look at Main(App)
In this source, self.root is considered as CircularProgressBar in here.
But, when I do print(self.root) it prints None.
It only recognize CircularProgressBar when self.root is in a function that is called by Clock.scheduled_interval(func, rate).
Does anyone know what is happening in here?
The explanation is very simple and is clearly explained in the docs:
root = None
The root widget returned by the build() method or by the load_kv()
method if the kv file contains a root widget.
From the above it is understood that the root is the element that is returned in the build() method, so before something returns that function the root will be None, so when you print self.root within build() or call a function that prints self.root before returning that function you will always get None. After returning the root it will be what you returned, that is, the container an object of the class CircularProgressBar.

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