Kivy Popup and Drag Behavior - python

So I am very new to Kivy, and it is pretty frustrating so far.....anyway, I am trying to now make a popup that I can drag and move around, and I don't understand what is going on.... when I call popup.open() in the onButtonPress function, the popup is closeable through the dismiss action, though I lose dragable functionality....when I add the popup directly to the main window through self.layout.add_widget(popup), I am able to move the popup but then am not able to close it....I'm guessing the open() call is redefining the dragable window? Is this true? If not what is happening and how do I fix it?
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.image import Image
from kivy.core.window import Window
from kivy.uix.behaviors import DragBehavior
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.popup import Popup
class PopupExample(App):
# override the build method and return the root widget of this App
def build(self):
# Define a grid layout for this App
self.layout = FloatLayout()
# Add a button
self.button = Button(text ="Click for pop-up")
self.layout.add_widget(self.button)
# Attach a callback for the button press event
self.button.bind(on_press = self.onButtonPress)
return self.layout
def onButtonPress(self, button):
# print('opening')
layout = GridLayout(cols = 1, padding = 10)
img = Image(source="temp_plot.png")
closeButton = Button(text = "Close the pop-up")
layout.add_widget(img)
layout.add_widget(closeButton)
popup = MoveableImage( title ='Demo Popup', content = layout, size_hint =(None, None), size = (400,400))
popup.open()
#self.layout.add_widget(popup)
#closeButton.bind(on_press = self.remove_widget(popup))
class MoveableImage(DragBehavior,Popup):
def __init__(self, **kwargs):
super(MoveableImage, self).__init__(**kwargs)
self.drag_timeout = 10000000
self.drag_distance = 0
self.drag_rectangle = [self.x, self.y, self.width, self.height]
def on_pos(self, *args):
self.drag_rectangle = [self.x, self.y, self.width, self.height]
def on_size(self, *args):
self.drag_rectangle = [self.x, self.y, self.width, self.height]
# Run the app
if __name__ == '__main__':
PopupExample().run()

If you want to drag the Popup, adding it to the App layout is probably the best way to go. Of course, then you don't really need to use the Popup, just use some Layout. If you go that route, deleting the Popup can be accomplished by defining the Button binding like this:
closeButton.bind(on_press = lambda x: self.layout.remove_widget(popup))
The lambda is just there to suck up the button instance arg that on_press adds, so you won't get errors about too many arguments to the remove_widget() method.
The modalView class (base of the Popup) has a method that keeps the Popup centered. Looks like the DragBehavior will work, if you over-ride that method. So, if your add:
def _align_center(self, *l):
pass
to your MoveableImage class, I think you will then be able to drag it. The open() method of the ModalView does some bindings to insure that the _align_center() method is called on any position or size change. Since adding the Popup to a Layout does not call the open() method, those bindings are never set up.

Related

Why does the button print twice? (Kivy Python)

I'm trying to make a button and I'm doing this print as a test, I'm not why it prints twice?
I'm new to Kivy so I'm not too sure on what I'm doing, any help is appreciated thank you.
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
def bannedBooksMain(self, instance):
print('The button test is being pressed')
class mainApp(GridLayout):
def __init__(self, **kwargs):
super(mainApp, self).__init__(**kwargs)
self.cols = 2
btn1 = Button(text='Home Page')
btn1.bind(state=bannedBooksMain)
self.add_widget(btn1)
class MyApp(App):
def build(self):
return mainApp()
MyApp().run()
btn1.bind(state=bannedBooksMain)
This (state=) will initiate a callback whenever the state property changes, this happens for both button press and release, which is why you're seeing it twice
You should use on_press= or on_release= instead, depending on when you want the action fired.
Personally, I prefer the on_release= combined with always_release being false. That way, if I inadvertently press the button, I can move the mouse point away from the button before releasing, and there will be no callback.
See here for further detail.

Kivy size_hint not working for all Layouts

I was just learning Kivy from Youtube, and The size_hint attribute in all layouts, buttons, widgets is not working. How do I fix this issue?
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.core.window import Window
from kivy.graphics import Ellipse,Color,Line
from random import randint
class PaintApp(App):
def build(self):
rootWindow=Widget()
self.painter=PaintWindow()
# clearBtn=Button(text="Clear",color=(1,0,0,1),font_size="30sp",background_color=(0,1,0,1),pos =(300, 250))
print(f"SIZE OF PAINTER: {self.painter.width},{self.painter.height}")
#####################################################################################################
'''
This Button's size_hint is not working on running on my device or due to some kivy issue.
'''
clearBtn = Button(text="Clear Screen",size_hint=(0.2,0.2),pos_hint=(0.8,0.8))
#####################################################################################################
clearBtn.bind(on_release=self.clearCanvas)
rootWindow.add_widget(self.painter)
rootWindow.add_widget(clearBtn)
return rootWindow
def clearCanvas(self,obj):
print(obj)
self.painter.canvas.clear()
class PaintWindow(Widget):
def on_touch_down(self, touch):
self.canvas.add(Color(rgb=(randint(0,255)/255,randint(0,255)/255,randint(0,255)/255)))
# d=20
# self.canvas.add(Ellipse(pos=(touch.x-d/2,touch.y-d/2),size=(d,d)))
touch.ud['line']= Line(points=(touch.x,touch.y),width=5)
self.canvas.add(touch.ud['line'])
def on_touch_move(self, touch):
touch.ud["line"].points+=[touch.x,touch.y]
if __name__ == "__main__":
PaintApp().run()
I'm a complete beginner at kivy. Here is an image of Kivy window I get as an output:
I wanted it to get on kind of to the top right corner of screen.
I tried it in kvlang but still showing the same result.
The size_hint does work for Layouts, but Widget is not a Layout. The Layout that interprets the size_hint is the Layout that contains the Widget with the size_hint. So, when you provide a size_hint to a Button:
clearBtn = Button(text="Clear Screen",size_hint=(0.2,0.2),pos_hint=(0.8,0.8))
The size_hint is not used by the Button itself, it is used by the Layout when you do something like:
l.add_widget(clearBtn)
where l is a Layout. If you do:
rootWindow.add_widget(clearBtn)
The size_hint will not be honored, because rootWindow is a Widget, but not a Layout. Try defining rootWindow as:
rootWindow = RelativeLayout()

Kivy | Can't set 2 buttons on_press binding in screen [duplicate]

I would like to know how to change screens using an on_press event binded to a button, without using a KV file/KV language.
I have read through the Kivy documentation, but have only been able to find solutions using a KV file.
Example:
on_press: root.manager.current = 'screen2'
I can also change the screen in the main python file using:
screenmanager.current = 'screen2'
But I cant figure out how to achieve the same using a button.
A working example with two screens, no kv file everything done in Python:
import kivy
kivy.require('1.8.0')
from kivy.app import App
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.properties import ObjectProperty
class ScreenOne(Screen):
def __init__ (self,**kwargs):
super (ScreenOne, self).__init__(**kwargs)
my_box1 = BoxLayout(orientation='vertical')
my_label1 = Label(text="BlaBlaBla on screen 1", font_size='24dp')
my_button1 = Button(text="Go to screen 2",size_hint_y=None, size_y=100)
my_button1.bind(on_press=self.changer)
my_box1.add_widget(my_label1)
my_box1.add_widget(my_button1)
self.add_widget(my_box1)
def changer(self,*args):
self.manager.current = 'screen2'
class ScreenTwo(Screen):
def __init__(self,**kwargs):
super (ScreenTwo,self).__init__(**kwargs)
my_box1 = BoxLayout(orientation='vertical')
my_label1 = Label(text="BlaBlaBla on screen 2",font_size='24dp')
my_button1 = Button(text="Go to screen 1",size_hint_y=None, size_y=100)
my_button1.bind(on_press=self.changer)
my_box1.add_widget(my_label1)
my_box1.add_widget(my_button1)
self.add_widget(my_box1)
def changer(self,*args):
self.manager.current = 'screen1'
class TestApp(App):
def build(self):
my_screenmanager = ScreenManager()
screen1 = ScreenOne(name='screen1')
screen2 = ScreenTwo(name='screen2')
my_screenmanager.add_widget(screen1)
my_screenmanager.add_widget(screen2)
return my_screenmanager
if __name__ == '__main__':
TestApp().run()
One simple way to accomplish this is to define your own button subclass:
class ScreenButton(Button):
screenmanager = ObjectProperty()
def on_press(self, *args):
super(ScreenButton, self).on_press(*args)
self.screenmanager.current = 'whatever'
The on_press method is automatically called when the button is pressed, so the screenmanager's current property will be changed.
Then you can have code something like:
sm = ScreenManager()
sc1 = Screen(name='firstscreen')
sc1.add_widget(ScreenButton(screenmanager=sm))
sc2 = Screen(name='whatever')
sc2.add_widget(Label(text='another screen'))
sm.add_widget(sc1)
sm.add_widget(sc2)
Clicking the button should switch the screens as required.
Another way (which is probably how kv language actually does it) would be to manually use the bind method.
def switching_function(*args):
some_screen_manager.current = 'whatever'
some_button.bind(on_press=switching_function)
This would mean that switching_function is called whenever some_button is pressed. Of course there is a lot of flexibility here regarding how and when you define the function, so (for instance) you could do something more general like pass the screenmanager as the first argument to the function.
I didn't test this code and it isn't a complete app, but hopefully the meaning is clear. Either method should work fine, you can choose the way that seems most sensible. I might construct a more complete example later.
Another solution, was to use the setter method of EventDispatcher, to get a reference to the setter function for screen_manager.current
button.bind(on_press=partial(sm.setter('current'), (sm, 'whatever'))
of course, it's not very sexy, that's why kv is often a cleaner solution to these things, but it should work.
ps: in case you don't know about it, partial comes from the functools module, and it's often useful to build these kind of callbacks with a preloaded parameter.

Kivy - MotionEvent: on_mouse_pos

Does kivy support MouseEvent that is triggered on mouse_pos change without pressing mouse button?
I found in documentation this:
def on_motion(self, etype, motionevent):
# will receive all motion events.
pass
Window.bind(on_motion=on_motion)
You can also listen to changes of the mouse position by watching mouse_pos.
However I cant implement it. I managed to bind it and add to on_motion function 'print('Hello world')' but it was triggered only by pressing-type events.
Thanks in advance
Solution
Bind mouse_pos to a callback. Please refer to example for details.
Window.bind(mouse_pos=self.mouse_pos)
Example
main.py
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.core.window import Window
class MousePosDemo(BoxLayout):
def __init__(self, **kwargs):
super(MousePosDemo, self).__init__(**kwargs)
self.label = Label()
self.add_widget(self.label)
Window.bind(mouse_pos=self.mouse_pos)
def mouse_pos(self, window, pos):
self.label.text = str(pos)
class TestApp(App):
title = "Kivy Mouse Pos Demo"
def build(self):
return MousePosDemo()
if __name__ == "__main__":
TestApp().run()
Output
You actually want to do:
Window.bind(mouse_pos=on_motion)

Properly Positioning Popup Widgets in PyQt

his has plagued me for eons, mostly due to how many combinations of methodologies there are for moving widgets and whatnot. Essentially I have a simple widget that I'd like to be able to pop up in specific areas of my app. Problem is I can never seem to get it to pop up where I want it. Additionally, I'd like to set it up in a way where I can adjust the "pointer" side of it based on whether it's popping up to point at a widget in the top-left of the app versus, say, the bottom-right.
Ideally, I'd be able to place the popup nearly adjacent to the edges of the parent widget, and anchor it based on where it is. Here's what I've been trying.
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys
class popup(QWidget):
def __init__(self, parent = None, widget=None):
QWidget.__init__(self, parent)
layout = QGridLayout(self)
button = QPushButton("Very Interesting Text Popup. Here's an arrow ^")
layout.addWidget(button)
self.move(widget.rect().bottomLeft())
class Window(QWidget):
def __init__(self):
QWidget.__init__(self)
self.button = QPushButton('Hit this button to show a popup', self)
self.button.clicked.connect(self.handleOpenDialog)
self.button.move(250, 50)
self.resize(600, 200)
def handleOpenDialog(self):
self.popup = popup(self, self.button)
self.popup.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
win = Window()
win.show()
sys.exit(app.exec_())
This code generates a button that's randomly in the middle of the widget. What I'm trying to get is, in this example, the popup to show under the button with its "pivot" in the top right such that the arrow in the popup button would be pointing to the bottom right corner of the widget. However it's popping up in the top left of the Window instead. In all of my messing around with .move, .setGeometry, and playing with QRect, I can't for the life of me figure this out. Huge kudos to whoever can lend a hand. Thanks!
I know this is old, but I was searching for this recently and this is the best answer; I have a useful addition (for anyone else searching for this recipe!)
I implemented it as a mixin, which I think gives more flexibility to your dialogs:
class PopupDialogMixin(object): # will not work (with PySide at least) unless implemented as 'new style' class. I.e inherit from object
def makePopup(callWidget):
"""
Turns the dialog into a popup dialog.
callWidget is the widget responsible for calling the dialog (e.g. a toolbar button)
"""
self.setContentsMargins(0,0,0,0)
self.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.Popup)
self.setObjectName('ImportDialog')
# Move the dialog to the widget that called it
point = callWidget.rect().bottomRight()
global_point = callWidget.mapToGlobal(point)
self.move(global_point - QtCore.QPoint(self.width(), 0))
Your custom dialog would then inherit from both QtCore.QDialog and PopupDialogMixin. This gives you the option to use your dialog in the 'normal' way or make it a popup dialog. e.g:
dlg = MyDialog(self)
dlg.makePopup(self.myButton)
I think implementing it as a mixin gives a number of benefits:
No need to write the 'popup' code for each custom dialog you want as a popup
'Default' behaviour of the dialog is preserved - e.g. if you want to reuse it somewhere else as a 'regular' dialog, you just use it like normal
No need to pass anything extra to __init__ other than parent.
Here you go - the comments kind of explain the logic behind it - since the question is an example and about the positioning, I kept the rest of the code the same except the popup class, but just to mention cause its a pet peeve - you shouldn't import * (ever) but especially with something as big as PyQt4.QtCore/QtGui...
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys
class popup(QWidget):
def __init__(self, parent = None, widget=None):
QWidget.__init__(self, parent)
layout = QGridLayout(self)
button = QPushButton("Very Interesting Text Popup. Here's an arrow ^")
layout.addWidget(button)
# adjust the margins or you will get an invisible, unintended border
layout.setContentsMargins(0, 0, 0, 0)
# need to set the layout
self.setLayout(layout)
self.adjustSize()
# tag this widget as a popup
self.setWindowFlags(Qt.Popup)
# calculate the botoom right point from the parents rectangle
point = widget.rect().bottomRight()
# map that point as a global position
global_point = widget.mapToGlobal(point)
# by default, a widget will be placed from its top-left corner, so
# we need to move it to the left based on the widgets width
self.move(global_point - QPoint(self.width(), 0))
class Window(QWidget):
def __init__(self):
QWidget.__init__(self)
self.button = QPushButton('Hit this button to show a popup', self)
self.button.clicked.connect(self.handleOpenDialog)
self.button.move(250, 50)
self.resize(600, 200)
def handleOpenDialog(self):
self.popup = popup(self, self.button)
self.popup.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
win = Window()
win.show()
sys.exit(app.exec_())

Categories

Resources