I want to rotate a button with the pure python code instead of the kvlang.
With kvlang we can rotate a button as shown in the example.
The button is rotated 45 degree around its own center. Below is the original code:
from kivy.app import App
from kivy.lang import Builder
kv = '''
FloatLayout:
Button:
text: 'hello world'
size_hint: None, None
pos_hint: {'center_x': .5, 'center_y': .5}
canvas.before:
PushMatrix
Rotate:
angle: 45
origin: self.center
canvas.after:
PopMatrix
'''
class RotationApp(App):
def build(self):
return Builder.load_string(kv)
RotationApp().run()
But when I try to rewrite this example with the pure python code as below, the button is rotated around somewhere else, as shown here:
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.button import Button
from kivy.graphics import PushMatrix, PopMatrix, Rotate
class MyButton(Button):
def __init__(self):
super().__init__()
self.text = 'hello world'
self.size_hint = (None, None)
self.pos_hint = {'center_x': .5, 'center_y': .5}
with self.canvas.before:
PushMatrix()
Rotate(origin=self.center, angle=45)
with self.canvas.after:
PopMatrix()
class RotationApp(App):
def __init__(self):
super().__init__()
self.layout = FloatLayout()
self.button = MyButton()
self.layout.add_widget(self.button)
def build(self):
return self.layout
RotationApp().run()
The above two pieces of code are not producing the same result. Is there anything we did wrong?
UPDATE:
The puzzle is solved as suggested by #inclement, solution as below:
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.button import Button
from kivy.graphics import PushMatrix, PopMatrix, Rotate
class MyButton(Button):
def __init__(self):
super().__init__()
self.text = 'hello world'
self.size_hint = (None, None)
self.pos_hint = {'center_x': .5, 'center_y': .5}
with self.canvas.before:
PushMatrix()
# Rotate(origin=self.center, angle=45) # previous approach
self.rotation = Rotate(origin=self.center, angle=45)
self.bind(center=lambda _, value: setattr(self.rotation, "origin", value))
with self.canvas.after:
PopMatrix()
class RotationApp(App):
def __init__(self):
super().__init__()
self.layout = FloatLayout()
self.button = MyButton()
self.layout.add_widget(self.button)
def build(self):
return self.layout
RotationApp().run()
In your Python code, self.center is evaluated just once during the __init__. In the kv code, a binding is automatically created to reset the Rotate instruction's origin property every time it changes.
You need to put that missing functionality in the Python code, something like self.rotation = Rotate(...) and self.bind(center=lambda instance, value: setattr(self.rotation, "origin", value)) (although I'm sure you can think of a nicer way to set that up, that's just the inline example).
Related
could you tell me how to make clicking the button (MDIconButton) change the icon.
I tried this by changing the icon variable:
class MyButton(MDIconButton):
def __init__(self):
super().__init__(*args, **kwargs)
self.icon = "path to first image"
self.alternative = "path to second image"
self.icon_size = 300
self.radius = 30
self.size_hint = [.05, .05]
def on_press(self):
self.icon, self.alternative = self.alternative, self.icon
But after that the alignment is lost and the icon goes to the bottom left corner and there is no way to change it.
Please help me so much.
The following example contains an MDIconButton, whose icon changes after clicking it, while keeping its original size and position, as intended.
from kivy.uix.boxlayout import BoxLayout
from kivymd.app import MDApp
from kivy.lang import Builder
Builder.load_string('''
<UpdateIcon>:
orientation: 'vertical'
MDIconButton:
id: iconButton
icon: 'language-python'
pos_hint: {'x':.5, 'y':.5}
size_hint: (.05, .05)
icon_size: '300sp'
on_press: root.updateIcon('android')
''')
class UpdateIcon(BoxLayout):
def __init__(self, **kwargs):
super(UpdateIcon,self).__init__(**kwargs)
pass
def updateIcon(self, newIcon):
self.ids.iconButton.icon = newIcon
class TestApp(MDApp):
def build(self):
self.title = "Change Icon"
return UpdateIcon()
if __name__ == '__main__':
TestApp().run()
I am trying to initialize the widget class by placing a button at the root.center. Strangely, root.size during initialization is only (100,100). When it's rendered, the actual size is (600,800). So my button is placed in the left bottom instead of in the center.
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import NumericProperty, ReferenceListProperty
from kivy.uix.widget import Widget
w = Builder.load_string('''
<MyWidget>:
canvas.before:
Color:
rgba: 1, 0.3, 0.3, 1
Rectangle:
size: self.size
pos: self.pos
Button:
id: score_button
center: root.score_button_center
text:'Best Scores'
''')
class MyWidget(Widget):
score_center_y = NumericProperty(-100)
score_center_x = NumericProperty(-100)
score_button_center = ReferenceListProperty(score_center_x, score_center_y)
hide_center = (-100, -100)
def __init__(self, **kwargs):
super(MyWidget, self).__init__(**kwargs)
self.score_button_center = self.width, self.height
class MyApp(App):
def build(self):
g = MyWidget()
return g
if __name__ == '__main__':
MyApp().run()
Just change:
center: root.score_button_center
to:
center: root.center
That will take advantage of the kivy feature that sets up bindings to adjust the Button position whenever the root widget changes size or position.
Hello and thanks for reading. I have trouble about move widget position on screen. Actually i checked widget's position, its changing as i want but at the screen i can't see any changing. I tried this too, in for loop self.remove_widget(i) and then self.add_widget(i). But there is nothing changed too.
.py file :
#!/usr/bin/python
# -*- coding: UTF-8 -*-
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager,Screen
from kivy.uix.widget import Widget
from kivy.graphics import Color,Rectangle
from random import randint
class Page(Screen):
def on_enter(self, *args):
self.list1 = []
self.list2 = []
self.currentball = 0
def add(self):
self.currentball += 1
self.list1.append('Ball[{}]'.format(self.currentball))
self.list2.append(self.list1[len(self.list1)-1])
print('List1 => ',self.list1)
print('List2 => ',self.list2)
#Widget oluşturalım:
self.list2[len(self.list1)-1] = Widget(size_hint=(.1,.1),pos=(randint(100,200),randint(250,500)))
with self.list2[len(self.list1)-1].canvas:
Color(1,1,1,1)
Rectangle(source='worker.png',size=self.list2[len(self.list1)-1].size,pos=self.list2[len(self.list1)-1].pos)
self.add_widget(self.list2[len(self.list1)-1])
def move(self):
for i in self.list2:
i.pos[0] += randint(0,20)
print(i.pos)
def remove(self):
self.update()
class ScMn(ScreenManager):
pass
class test2(App):
def build(self):
return ScMn()
if __name__ == '__main__':
test2().run()
.kv file :
<ScMn>:
Page:
name: 'page'
id: pageid
<Page>:
RelativeLayout:
Button:
size_hint: .1,.1
pos: 0,0
text: 'Add'
on_press: root.add()
Button:
size_hint: .1,.1
pos_hint:{'x':.1,'y':0}
text: 'Move'
on_press: root.move()
Button:
size_hint: .1,.1
pos_hint:{'x':.2,'y':0}
text: 'RemoveLast'
on_press: root.remove()
Thanks for answering already...
Implement a custom widget class, e.g. MyWidget() with method to update pos and size
Replace Widget(...) with MyWidget(...)
Remove with self.list2[len(self.list1)-1].canvas: ... because it is done in the new class MyWidget()
Snippets
class MyWidget(Widget):
def __init__(self, **kwargs):
super(MyWidget, self).__init__(**kwargs)
with self.canvas:
self.rect = Rectangle(pos=self.pos, size=self.size)
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
class Page(Screen):
...
def add(self):
...
self.list2[len(self.list1) - 1] = MyWidget(size_hint=(.1, .1), pos=(randint(100, 200), randint(250, 500)))
self.add_widget(self.list2[len(self.list1) - 1])
The issue is a simple one, getting Kivy to integrate the Timer1 code as a label in FloatLayout.
I have this .py file:
import kivy
kivy.require('1.10.0')
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.image import Image
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.stacklayout import StackLayout
from kivy.clock import Clock
from kivy.properties import StringProperty, NumericProperty, ObjectProperty
from digitalclock import DigitalClock
from kivy.animation import Animation
import time
class IntroScreen(Screen):
pass
class ContScreen(Screen):
pass
class ScreenManagement(ScreenManager):
pass
backbone = Builder.load_file("main.kv")
class Status(FloatLayout):
_change = StringProperty()
_tnd = ObjectProperty(None)
def update(self, *args):
self.time = time.asctime()
self._change = str(self.time)
self._tnd.text = str(self.time)
print (self._change)
class Timer1(Label):
a = NumericProperty(10) # seconds
color = 1, 1, 1, 1
font_size = 50
def start(self):
Animation.cancel_all(self) # stop any current animations
self.anim = Animation(a=0, duration=self.a)
def finish_callback(animation, incr_crude_clock):
incr_crude_clock.text = "COOL"
self.anim.bind(on_complete=finish_callback)
self.anim.start(self)
def on_a(self, instance, value):
self.text = str(round(value, 1))
class XGApp(App):
time = StringProperty()
def update(self, *args):
self.time = str(time.asctime())
def build (self):
Clock.schedule_interval(self.update, 1)
t1 = Timer1()
return backbone
xApp = XGApp()
if __name__ == "__main__":
xApp.run()
and the .kv:
<ContScreen>:
DigitalClock:
pos_hint: {'center_x': 0.1, 'center_y': 0.9}
size_hint: (0.075, 0.075)
StackLayout
orientation: "tb-rl"
spacing: 15
Button:
text: "1"
size_hint: None, .16
width: 225
on_press:
self.background_color = (1.7, 0, 1.7, 1)
t1.start()
I am trying to get the Timer1 aspect as a label in FloatLayout on the .kv, which appears as the button is triggered. At the moment, what I've been getting is the Timer1 as a full-screen label.
Please help!
Solution
Move the design view of Timer's Label from Python code into kv file.
Add constructor for class Timer and accepts arguments, root, instance, duration, bg_colour
In kv file, pass arguments root (screen 'cont'), instance of button, duration, background colour when instantiating Timer
In build method, remove t1 = Timer1()
Example
main.py
import kivy
kivy.require('1.11.0')
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.label import Label
from kivy.uix.floatlayout import FloatLayout
from kivy.clock import Clock
from kivy.properties import StringProperty, NumericProperty, ObjectProperty
from kivy.animation import Animation
import time
class IntroScreen(Screen):
pass
class ContScreen(Screen):
pass
class ScreenManagement(ScreenManager):
pass
class Status(FloatLayout):
_change = StringProperty()
_tnd = ObjectProperty(None)
def update(self, *args):
self.time = time.asctime()
self._change = str(self.time)
self._tnd.text = str(self.time)
print (self._change)
class Timer(Label):
a = NumericProperty() # seconds
def __init__(self, root, instance, duration, bg_colour, **kwargs):
super(Timer, self).__init__(**kwargs)
self.obj = instance
self.a = duration
self.root = root
self.obj.disabled = True # disable widget/button
self.obj.background_color = bg_colour
self.root.add_widget(self) # add Timer/Label widget to screen, 'cont'
def animation_complete(self, animation, widget):
self.root.remove_widget(widget) # remove Timer/Label widget to screen, 'cont'
self.obj.background_color = [1, 1, 1, 1] # reset to default colour
self.obj.disabled = False # enable widget/button
def start(self):
Animation.cancel_all(self) # stop any current animations
self.anim = Animation(a=0, duration=self.a)
self.anim.bind(on_complete=self.animation_complete)
self.anim.start(self)
def on_a(self, instance, value):
self.text = str(round(value, 1))
class XGApp(App):
time = StringProperty()
def update(self, *args):
self.time = str(time.asctime())
def build (self):
Clock.schedule_interval(self.update, 1)
return Builder.load_file("main.kv")
if __name__ == "__main__":
XGApp().run()
kv file
#:import DigitalClock digitalclock
#:import Timer main.Timer
<ContScreen>:
DigitalClock:
pos_hint: {'center_x': 0.1, 'center_y': 0.9}
size_hint: (0.075, 0.075)
StackLayout
orientation: "tb-rl"
spacing: 15
Button:
text: "1"
size_hint: None, .16
width: 225
on_press:
Timer(root, self, 5, [0.17, 1.7, 0, 1]).start()
Button:
text: "2"
size_hint: None, .16
width: 225
on_press:
Timer(root, self, 10, [1.7, 0, 1.7, 1]).start()
<Timer>:
canvas.before:
Color:
rgba: 0, 0, 0.5, 1 # 50% blue
Rectangle:
size: self.size
pos: self.pos
size_hint: 0.3, .1
font_size: 50
pos_hint: {'center_x': 0.5, 'center_y': 0.5}
Output
One way to make it work is to have the Button create the timer. Start by adding a start_timer() method to the ContScreen class:
class ContScreen(Screen):
def start_timer(self, *args):
timer = Timer1(size_hint=(0.2, 0.2))
self.add_widget(timer)
timer.start()
For this to work, make three other changes:
Change your main.kv file to make a root widget (eliminate the <> surrounding ContScreen).
Change the on_press for the button in the .kv file by replacing t1.start() with root.start_timer().
Eliminate the t1 = Timer1() statement from your build method in the XGApp class.
Another approach would be to create the Timer1 in the .kv file, and just start it running when the Button is pressed. To do this, change your .kv file to include a Timer:
ContScreen:
DigitalClock:
pos_hint: {'center_x': 0.1, 'center_y': 0.9}
size_hint: (0.075, 0.075)
StackLayout
orientation: "tb-rl"
spacing: 15
Button:
text: "1"
size_hint: None, .16
width: 225
on_press:
self.background_color = (1.7, 0, 1.7, 1)
timer.start()
Timer1:
id: timer
text: '0.0'
size_hint: (0.2, 0.2)
Move your backbone = Builder.load_file("main.kv") to after the definition of the Timer1 class. And change the ContScreen class back to:
class ContScreen(Screen):
pass
For some reason in kivy when you create a screen class and add a widget to it, specifically an image you get an image 10x bigger than the original for some reason compared to when you make a widget class and add that widget class as a child to the screen class. Here is my code for the kv file:
<StartScreen>
# Start Screen
name:'Start'
orientation: 'vertical'
FloatLayout:
id: Start_Layout
Image:
id: Start_Background
source: r'Images\Battle.jpg'
keep_ratio: True
allow_stretch: True
size: root.size
<MainScreen>
name: 'Main'
orientation: 'vertical'
FloatLayout:
Image:
source: r'Images\Button.png'
allow_stretch: True
keep_ratio: False
size: 100, 100
and for the python gui file...
from kivy.uix.widget import Widget
from kivy.uix.image import Image
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.animation import Animation
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.clock import Clock
from kivy.graphics.context_instructions import Color
from kivy.graphics.vertex_instructions import *
from kivy.core.window import Window
from kivy.app import App
from kivy.lang import Builder
import kivy
kivy.require('1.9.1')
VERSION = '1.9.1'
class GenericButton(Widget):
Builder.load_file('Button.kv')
def __init__(self, **kwargs):
super(GenericButton, self).__init__(**kwargs)
self.Button = self.ids['Button']
self.size = Window.size
def on_touch_down(self, touch):
self.Button.source = r'Images\ButtonPressed.png'
def on_touch_up(self, touch):
self.Button.source = r'Images\Button.png'
class wid(Widget):
def __init__(self, **kwargs):
super(wid, self).__init__(**kwargs)
self.Button = Image(source='Images\Animatio\glow.gif', allow_stretch=False, keep_ratio=True) (pretend its indented cus im new and it wouldn't let me add it to the code block)
self.add_widget(self.Button)
class StartScreen(Screen):
def __init__(self, **kwargs):
super(StartScreen, self).__init__(**kwargs)
#self.Layout = self.ids['Start_Layout']
#self.size = Window.size
#self.Layout.add_widget(GenericButton())
#self.ids['Start_Button'] = self.Layout.children[0]
print self.ids
#print self.ids.Start_Button.size
print self.size[0]/2, self.size[1]/2
class MainScreen(Screen):
def __init__(self, **kwargs):
super(MainScreen, self).__init__(**kwargs)
self.size = Window.size
def on_touch_down(self, touch):
self.Button.source = r'Images\ButtonPressed.png'
def on_touch_up(self, touch):
self.Button.source = r'Images\Button.png'
class ScreenManager(ScreenManager):
def __init__(self, **kwargs):
super(MCGMScreenManager, self).__init__(**kwargs)
Builder.load_file('Gui.kv')
self.add_widget(StartScreen())
self.add_widget(MainScreen())
And the app runs in the main file which i dont see a need to post. But an important thing might be that the app root class is ScreenManager
edit: i messed around a bit and i did this in python but i cleared the children of GenericButton and added the button that GenericButton used to own as a child of StartScreen and same result, a huge unresponsive image.
<MainScreen>
name: 'Main'
orientation: 'vertical'
FloatLayout:
Image:
source: r'Images\Button.png'
allow_stretch: True
keep_ratio: False
size: 100, 100
I didn't check if it's causing your particular issue, but the Image here doesn't take size 100, 100 because its parent (FloatLayout) is a layout class that automatically sets the size and position of its children. In this case, Image will automatically be resized to fill the FloatLayout.
To prevent this, add size_hint: None, None to the Image, to disable automatic resizing in both the horizontal and vertical directions. This generally applies whenever you add something to a Layout.