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.
Related
I would like to draw something in canvas from .py by using class 'BotRightCanvas'. The initial widgets are all defined in .kv
Problem is when I finally declare BotRightCanvas in .kv, the size of the canvas widget is not coming proper (still 100x100). I would like the green rectangle to coincide with the cyan one.
Here's .py
from kivy.graphics import Color
from kivy.graphics.vertex_instructions import Line, Rectangle, Ellipse
from kivy.metrics import dp
from kivy.uix.widget import Widget
from kivy.uix.boxlayout import BoxLayout
from kivy.core.window import Window
class TestOut(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def getSize(self, id):
cSize = id.size
print('win ' + str(Window.size))
print('canvas ' + str(id.size))
print('cSize ' + str(cSize))
class BotRightCanvas(Widget):
def __init__(self, **kwargs):
super().__init__(**kwargs)
with self.canvas:
Color(0, 1, 0, 1)
Line(circle=(self.width / 2, self.height / 2, 25), width=2)
Line(rectangle=(0, 0, self.width, self.height), width=5)
class PlaygroundApp(App):
title = "blabla"
def build(self):
return TestOut()
if __name__ == "__main__":
PlaygroundApp().run()
and .kv
<TestOut>:
BoxLayout:
orientation: 'vertical'
Button:
text:'A'
height: dp(100)
size_hint: 1, None
BoxLayout:
Button:
text:'B'
width: dp(150)
size_hint: None, 1
RelativeLayout:
id: rel_lo
on_size: root.getSize(rel_lo)
Widget:
canvas:
Line:
rectangle: (0,0, self.width, self.height)
width: 2
canvas.before:
Color:
rgba: 0,1,1,1
BotRightCanvas:
I tried printing the final canvas shape in console, seems it initializes with size of 0x0 but later on gets correct size 650x500.
The console output
canvas [0.0, 0]
cSize [0.0, 0]
win (800, 600)
canvas [650.0, 500.0]
cSize [650.0, 500.0]
Any help? Thanks !!
EDIT: Would like to control canvas items from .py
from kivy.graphics import Color
from kivy.graphics.vertex_instructions import Line, Rectangle, Ellipse
from kivy.metrics import dp
from kivy.uix.widget import Widget
from kivy.uix.boxlayout import BoxLayout
from kivy.core.window import Window
from kivy.lang.builder import Builder
from kivy.app import App
class TestOut(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def getSize(self, id):
cSize = id.size
print('win ' + str(Window.size))
print('canvas ' + str(id.size))
print('cSize ' + str(cSize))
class BotRightCanvas(Widget):
pass
Builder.load_string(
'''
<BotRightCanvas>:
canvas:
Color:
rgba:0, 1, 0, 1
Line:
circle:self.width / 2, self.height / 2, 25
width:2
Line:
rectangle:0, 0, self.width, self.height
width:5
<TestOut>:
BoxLayout:
orientation: 'vertical'
Button:
text:'A'
height: dp(100)
size_hint: 1, None
BoxLayout:
Button:
text:'B'
width: dp(150)
size_hint: None, 1
RelativeLayout:
id: rel_lo
on_size: root.getSize(rel_lo)
Widget:
canvas:
Line:
rectangle: (0,0, self.width, self.height)
width: 2
canvas.before:
Color:
rgba: 0,1,1,1
BotRightCanvas:
size:rel_lo.size
'''
)
class PlaygroundApp(App):
title = "blabla"
def build(self):
return TestOut()
for line in open('2.py').readlines():
print(' '+line)
if __name__ == "__main__":
PlaygroundApp().run()
I need to add a checkbox to this Kivy Canvas but it don"t know how to do it properly. I need the checkbox to display the name of all the file in the folder Data. Then the user check the file he wants and then by pressing a button (here the red one) it return a list of the file name and call some functions. Here is where i am right now.
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.image import Image
from kivy.uix.widget import Widget
from kivy.metrics import dp
from kivy.lang import Builder
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.base import runTouchApp
from kivy.uix.recycleview import RecycleView
from kivy.uix.popup import Popup
from kivy.uix.progressbar import ProgressBar
from kivy.uix.boxlayout import BoxLayout
from kivy.clock import Clock
from kivy.uix.button import Button
from kivy.uix.popup import Popup
from kivy.uix.widget import Widget.
from kivy.properties import ObjectProperty
import time
import os
Builder.load_string('''<Game>
ShootButton:
<ShootButton>
Image:
source: 'logo.jpg'
allow_stretch: True
size: 300,300
pos: 300, 400
Image:
source: 'livre.jpg'
allow_stretch: True
size: 350, 350
pos: 490,130
Button:
text: "Modifier Correspondance"
size: 240,50
font_size: 20
pos: 100,300
background_color :0, 1, 0, 1,
on_press: root.shoot()
Button:
text: "Liste Fichiers"
size: 240,50
font_size: 20
pos: 100,400
background_color :0, 1, 0, 1,
on_press: root.fire_popup()
Button:
text: "Ajouter Fichier"
size: 240,50
font_size: 20
pos: 100,200
background_color :0, 1, 0, 1,
on_press: root.shoot2()
Button:
text: "Generer PDF"
size: 180,50
font_size: 20
pos: 580,60
background_color :1, 0, 0, 1,
on_press: root.display()
''')
Window.size = (900, 600)
Window.clearcolor = (1, 1, 1, 1)
class Game(Widget):
pass
class SimplePopup(Popup):
pass
class SimplePopup2(Popup):
pass
class ShootButton(Widget):
def shoot(self):
shooting = Bullet()
shooting.bullet_fly()
def shoot2(self):
shooting = Bullet()
shooting.bullet_fly2()
class Bullet(Widget):
def bullet_fly(self):
print("test")
def bullet_fly2(self):
os.system("open .")
class MyApp(App):
def build(self):
return Game()
if __name__ == '__main__':
MyApp().run()
Ok, it's a lot of things you're asking for.
First we will need to create a layout with the labels as the files name and the checkbox to the corresponding file.
For this I'll use GridLayout, but you can do it as you want. In this layout we will need to get the files list, create the labels with the checkboxes, and, eventually, see what boxes are marked. So we will create a class with three functions and use a dict to link each box to its file:
class Grid(GridLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.cols = 2
self.files = self.get_files()
self.check_list = {}
self.create_checks()
def get_files(self):
# Get your files herre
return ['Hi', 'how', 'are', 'you?']
def create_checks(self):
# Create the checkboxes
for item in self.files:
self.add_widget(Label(text=item))
self.check_list[item] = CheckBox()
self.add_widget(self.check_list[item])
def get_actives(self):
# Check who is active (marked)
actives = []
for item in self.check_list:
if self.check_list[item].state == 'down':
actives.append(item)
return actives
Then we will ned to pass this class to our main content to display it for the user. For this I'll use a popup, but again, you can do as you please. So I'll start adding our content to a popup, and creating a function to call it.
class ShootButton(Widget):
def __init__(self, **kwargs):
super().__init__(**kwargs)
# Create popup with grid content
self.pop_content = Grid()
self.popup = Popup(title='', separator_height=0,
size_hint=(.8, .6),
content=self.pop_content)
def open_pop(self):
# You guessed! Opens the popup
self.popup.open()
Moslty done, now we just have to check which checkboxes are marked when the button is pressed. So I'll get your function display and use it to call our get_actives function:
def display(self):
actives = self.pop_content.get_actives()
print(actives)
And here is the full code:
from kivy.app import App
from kivy.core.window import Window
from kivy.lang import Builder
from kivy.uix.popup import Popup
from kivy.uix.widget import Widget
from kivy.uix.gridlayout import GridLayout
from kivy.uix.checkbox import CheckBox
from kivy.uix.label import Label
import os
Builder.load_string('''<Game>
ShootButton:
<ShootButton>
Image:
source: 'logo.jpg'
allow_stretch: True
size: 300,300
pos: 300, 400
Image:
source: 'livre.jpg'
allow_stretch: True
size: 350, 350
pos: 490,130
Button:
text: "Modifier Correspondance"
size: 240,50
font_size: 20
pos: 100,300
background_color :0, 1, 0, 1,
on_press: root.shoot()
Button:
text: "Liste Fichiers"
size: 240,50
font_size: 20
pos: 100,400
background_color :0, 1, 0, 1,
on_press: root.fire_popup()
Button:
text: "Ajouter Fichier"
size: 240,50
font_size: 20
pos: 100,200
background_color :0, 1, 0, 1,
on_press: root.shoot2()
Button:
text: "Generer PDF"
size: 180,50
font_size: 20
pos: 580,60
background_color :1, 0, 0, 1,
on_press: root.display()
''')
Window.size = (900, 600)
Window.clearcolor = (1, 1, 1, 1)
class Game(Widget):
pass
class SimplePopup(Popup):
pass
class SimplePopup2(Popup):
pass
class Grid(GridLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.cols = 2
self.files = self.get_files()
self.check_list = {}
self.create_checks()
def get_files(self):
# Get your files here
return ['Hi', 'how', 'are', 'you?']
def create_checks(self):
# Create the checkboxes
for item in self.files:
self.add_widget(Label(text=item))
self.check_list[item] = CheckBox()
self.add_widget(self.check_list[item])
def get_actives(self):
# Check who is active (marked)
actives = []
for item in self.check_list:
if self.check_list[item].state == 'down':
actives.append(item)
return actives
class ShootButton(Widget):
def __init__(self, **kwargs):
super().__init__(**kwargs)
# Create popup with grid content
self.pop_content = Grid()
self.popup = Popup(title='Choose files', separator_height=0,
size_hint=(.8, .6),
content=self.pop_content)
def shoot(self):
shooting = Bullet()
shooting.bullet_fly()
self.open_pop()
def shoot2(self):
shooting = Bullet()
shooting.bullet_fly2()
def fire_popup(self):
pass
def open_pop(self):
# You guessed! Opens the popup
self.popup.open()
def display(self):
actives = self.pop_content.get_actives()
print(actives)
class Bullet(Widget):
def bullet_fly(self):
print("test")
def bullet_fly2(self):
os.system("open .")
class MyApp(App):
def build(self):
return Game()
if __name__ == '__main__':
MyApp().run()
I want a rectangle to stick to the bottom left corner of an image. It works, but when I change window size the rectangle is little bit off. It is where image was before update. Here's the code:
from kivy.app import App, Builder
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.image import Image
from kivy.graphics import Rectangle
from kivy.graphics import Color
Builder.load_string(
'''
<Screen>:
canvas.before:
Color:
rgba: 1, 1, 1, 1
Rectangle:
pos: self.pos
size: self.size
FloatLayout:
Image:
id: im
keep_data: True
source: 'test.png'
allow_stretch: True
size_hint_y: 0.9
pos_hint: {'left':1, 'top':1}
Button:
text: 'Button'
size_hint_y: 0.1
pos_hint: {'x':0, 'y':0}
''')
class Screen(FloatLayout):
def __init__(self, **kwargs):
super(Screen, self).__init__(**kwargs)
with self.ids.im.canvas.after:
Color(1,0,1,.5)
self.rec = Rectangle(pos=(self.ids.im.center_x - self.ids.im.norm_image_size[0] / 2,
self.ids.im.center_y - self.ids.im.norm_image_size[1] / 2),
size=(self.ids.im.norm_image_size[0]/2,
self.ids.im.norm_image_size[1]/2))
self.bind(pos=self.update_canvas, size=self.update_canvas)
def update_canvas(self, *args):
self.rec.pos = (self.ids.im.center_x - self.ids.im.norm_image_size[0] / 2,
self.ids.im.center_y - self.ids.im.norm_image_size[1] / 2)
self.rec.size = (self.ids.im.norm_image_size[0]/2,
self.ids.im.norm_image_size[1]/2)
class QuestionApp(App):
def build(self):
return Screen()
if __name__ == "__main__":
QuestionApp().run()
I'm doing this in Python because I want to randomly spawn rectangles within the image, but I'm stuck here with rectangles when window resizing being wobbly.
If you want the rectangle to use the geometry of the image as reference then you must do the binding with the image, for this change:
self.bind(pos=self.update_canvas, size=self.update_canvas)
to
self.ids.im.bind(pos=self.update_canvas, size=self.update_canvas)
I'm attempting to drag some images arranged inside a GridLayout (which is indeed inside a ScrollView) to outer Layout.
The image to be dragged has its on_touch_down event defined, when image is clicked parent is changed from WidgetMenu to MainLayout so it can be dragged between those widgets. The current problem is when I touch the image, DragBehavior is lost.
The full code:
import kivy
kivy.require("1.9.1")
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.scrollview import ScrollView
from kivy.uix.widget import Widget
from kivy.uix.behaviors import DragBehavior
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.base import runTouchApp
from kivy.lang import Builder
Builder.load_string('''
<WidgetMenu>:
canvas.before:
Color:
rgb: 0.9,0.5,0.3
RoundedRectangle:
pos:self.pos
size: self.size
radius: [20,]
orientation: "vertical"
padding:30
ScrollView:
GridLayout:
cols:1
size_hint_y:None
row_default_height:root.height*.15
height:self.minimum_height
DragImage:
DragImage:
DragImage:
<DragImage>:
drag_rectangle: self.x, self.y, self.width, self.height
drag_timeout: 100000000
drag_distance: 0
size_hint:None,None
size:234,34
canvas:
Color:
rgb:1,0,1
Rectangle:
pos: self.pos
size: self.size
<MainLayout>:
canvas:
Color:
rgb:1,1,1
Rectangle:
size: self.size
pos: self.pos
WidgetMenu:
size_hint: 0.35,0.9
''')
class MainLayout(FloatLayout):
pass
class WidgetMenu(BoxLayout):
pass
class DragImage(DragBehavior,FloatLayout):
def on_touch_down(self,touch):
workspace = self.parent.parent.parent.parent
grid = self.parent
menu = self.parent.parent.parent
if "MainLayout" in str(workspace):
grid.remove_widget(self)
workspace.remove_widget(menu)
self.pos = Window.mouse_pos
workspace.add_widget(self)
return True
class ScrollApp(App):
def build(self):
return MainLayout()
ScrollApp().run()
Please help.
Two problems with your code are that you are not calling the super method in your on_touch_down, and placing the DragImage in the top level MainLayout changes the pos of the DragImage, thus changing its DragRectangle. That change in DragRectangle leaves the touch.pos outside of it, so DragBehavior thinks the touch is not on the DragImage.
I have fixed both those problems by calling the super method and changing the touch.pos before passing it to the super method. I also added some code to keep the DragImage in the same position relative to the mouse when it is clicked. Also added a call to self.collide_point() in order to ignore clicks not on the DragImage.
class DragImage(DragBehavior,FloatLayout):
def on_touch_down(self,touch):
if not self.collide_point(*touch.pos):
return False
workspace = self.parent.parent.parent.parent
grid = self.parent
menu = self.parent.parent.parent
if "MainLayout" in str(workspace):
grid.remove_widget(self)
workspace.remove_widget(menu)
# the following code assumes that workspace is the entire Window
self.x = Window.mouse_pos[0] - (touch.pos[0] - self.x)
self.y = Window.mouse_pos[1] - (touch.pos[1] - self.y)
workspace.add_widget(self)
touch.pos = Window.mouse_pos
return super(DragImage, self).on_touch_down(touch)
I'm making custom touch handling for my widget. The way I have it set up is that the initial size of the widget is set and fixed, however, later, I change it dynamically depending on the contents. I turned that feature down for now to shorten the code, it doesn't affect the problem. I've noticed something strange when I clicked at a blank spot to the right of the widget and the touch was registered, so I added a canvas background to inspect. Apparently, the actual widget boundaries are set much bigger than what the actual widget instance is, and I don't really know what's causing this. Those widgets are positioned in a FloatLayout, but I never set size_hints, only sizes. Could that be the case? How do I shorten the widget boundaries to what they're supposed to be?
Here is the entire source code, the problem is most likely in the kv definition:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen, ScreenManager, NoTransition
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.uix.scrollview import ScrollView
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.textinput import TextInput
global pr_msg_y
pr_msg_y = 5
Builder.load_string('''
<ScrollView>:
canvas:
Color:
rgba: 1, 1, 1, 1
Rectangle:
pos: self.pos
size: self.size
<Message>:
width: 500
canvas:
Color:
rgba: 1, 0, 0, 0.3
Rectangle:
pos: self.pos
size: self.size
BoxLayout:
pos: root.pos
height: self.height
canvas:
Color:
rgba: 0.6, 0.6, 0.6, 1
Rectangle:
pos: root.pos
size: self.size
TextInput:
pos: root.pos
size: root.size
id: msg
background_color: 0, 0, 0, 0
text: str(msg)
''')
class Message(Widget):
def __init__(self, **kwargs):
super(Message, self).__init__(**kwargs)
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
print("Touch within {}".format(self))
class Chat(Screen):
pass
class MessageInput(TextInput):
def __init__(self, **kwargs):
super(MessageInput, self).__init__(**kwargs)
class ChatApp(App):
def build(self):
def msg_in(btn):
global pr_msg_y
inst = Message()
inst.ids['msg'].text = "test" * 15
inst.width = 456 # something random, the sizing should be dynamic
inst.height = 40
for i in inst.walk():
i.height = inst.height
i.width = inst.width
inst.y = sv1_main.height - 5 - pr_msg_y - inst.height
msg_float.add_widget(inst)
pr_msg_y += inst.height + 5
Screens = ScreenManager(transition = NoTransition())
chat = Chat(name = "main")
sv1_main = ScrollView(pos_hint = {"top":0.87, "center_x":0.5},
size_hint = (0.97, 0.65))
msg_float = FloatLayout()
bt1_main = Button(pos_hint = {"top":0.097, "center_x":0.951},
on_press = msg_in)
Screens.add_widget(chat)
chat.add_widget(sv1_main)
sv1_main.add_widget(msg_float)
chat.add_widget(bt1_main)
return Screens
ChatApp().run()
size_hint does "overwrite" size. By default size_hint is set to 1,1, so if you only set size and you put the widget into a layout that recognizes size_hints, the widget will get the maximal available size.
Set size_hint = None, None to fix.