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)
Related
I would like to make a puzzle where someone has to follow a maze and collect letters which have to be typed in later as an answer. (Not collected by the program but just remembering which letters are passed and typing it in later while exiting the maze.)
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.gridlayout import GridLayout
from kivy.config import Config
Config.set('graphics', 'width', '806')
Config.set('graphics', 'height', '706')
class Touch(Widget):
def __init__(self, **kwargs):
super(Touch, self).__init__(**kwargs)
def on_touch_down(self, touch):
pass
def on_touch_down(self, touch):
pass
def on_touch_down(self, touch):
pass
class MyGrid(GridLayout):
pass
class DrawingWindow(App):
def build(self):
return MyGrid()
if __name__ == "__main__":
DrawingWindow().run()
<MyGrid>:
GridLayout:
cols:1
size:1000,706
canvas:
Rectangle:
size: 808,706
source: 'Puzzle.png'
I have found out how to draw a circle where your mouse is (not added to the code as I think a different method is probably going to be used), but how to make everything around a certain radius black so that it only reveals a small circle within this radius. Maybe using opacity within a radius is a sollution. If someone could push me in the right direction, that would be really helpful.
It does not have to stay revealed, rather not actually. Hopefully that makes it somewhat easier as well.
I hope I've stated clearly what the problem is. Thank you in advance for the time and effort.
You can use Stencil Instructions to accomplish that. Here is a simple example:
from kivy.app import App
from kivy.core.window import Window
from kivy.lang import Builder
from kivy.properties import NumericProperty, ListProperty
from kivy.uix.widget import Widget
kv = '''
RelativeLayout:
ImageWithHole:
id: hole
canvas:
StencilPush
Ellipse: # draw the hole
pos: self.hole_pos[0] - self.radius, self.hole_pos[1] - self.radius
size: self.radius*2, self.radius*2
StencilUse
Color:
rgba: 1,1,1,1
Rectangle:
source: 'Puzzle.png' # draw the maze
pos: 0,0
size: self.size
StencilUnUse
Ellipse: # erase the hole (must be identical to original draw above)
pos: self.hole_pos[0] - self.radius, self.hole_pos[1] - self.radius
size: self.radius*2, self.radius*2
StencilPop
'''
class ImageWithHole(Widget):
radius = NumericProperty(50)
hole_pos = ListProperty([400, 300])
class TestApp(App):
def build(self):
Window.bind(mouse_pos=self.on_motion)
return Builder.load_string(kv)
def on_motion(self, src, mouse_pos):
hole = self.root.ids.hole
hole.hole_pos = mouse_pos
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.
...
How can I give function path?
I couldn't find how to solve addressing.
Anyone have any idea how to fix this?
When I click on the green area only, I want to do the operation of the button in the white area.
...
'''python
main.py
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.widget import Widget
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.relativelayout import RelativeLayout
from kivy.uix.button import Button
from kivy.properties import ObjectProperty
from kivy.uix.textinput import TextInput
class Area(RelativeLayout):
pass
class Sec(RelativeLayout):
""" This is white area """
def chn(self):
self.ids.secbut.disabled = True
class Vec(RelativeLayout):
""" This is green area
I want the button to disable when the green area is clicked.
"""
def on_touch_down(self,touch):
if self.collide_point(*touch.pos):
Sec.ids.secbut.disabled = True
class runner(App):
def build(self):
self.area = Area()
return self.area
if __name__ == "__main__":
runner().run()
'''
...
runner.kv
...
'''kivy
<Area>:
size_hint: 1, 1
pos_hint: {"x":0,"y":0}
canvas:
Color:
rgba:0.9,0.9,0.9,1
Rectangle:
size:self.size
Sec:
Vec:
<Sec>:
size_hint: 1, 0.5
pos_hint: {"x":0,"y":0}
canvas:
Color:
rgba:0.9,0.8,0.7,1
Rectangle:
size:self.size
Button:
id:secbut
text:"click"
size_hint:0.5,0.5
<Vec>:
size_hint: 1, 0.5
pos_hint: {"x":0,"y":0.5}
canvas:
Color:
rgba:0.2,0.8,0.1,1
Rectangle:
size:self.size
'''
I couldn't find how to solve addressing.
You need to check if the touch actually collides with the area you care about.
if self.collide_point(*touch.pos):
self.ids.secbut.disabled = True
Kind of awkward, but you can do it by adding an id to your Sec like this:
<Area>:
size_hint: 1, 1
pos_hint: {"x":0,"y":0}
canvas:
Color:
rgba:0.9,0.9,0.9,1
Rectangle:
size:self.size
Sec:
id: sec
Vec:
Then use that in your on_touch_down() in Vec:
class Vec(RelativeLayout):
""" This is green area
I want the button to disable when the green area is clicked.
"""
def on_touch_down(self,touch):
if self.collide_point(*touch.pos):
App.get_running_app().root.ids.sec.ids.secbut.disabled = True
I'm trying to add a digital clock to my Kivy program, it seems to be having trouble.
Here is the .py:
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 ObjectProperty
from kivy.properties import StringProperty
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 XGApp(App):
time = StringProperty()
def update(self, *args):
self.time = str(time.asctime()) # + 'time'?
def build (self):
Clock.schedule_interval(self.update, 1)
return backbone
xApp = XGApp()
if __name__ == "__main__":
xApp.run()
and the .kv
<ContScreen>:
FloatLayout
size_hint: .1,.1
canvas.before:
Color:
rgba: 0,0,0,1
Rectangle:
pos: self.pos
size: self.size
Label:
text: app.time
ContScreen is the title of the screen I want to show the clock on, it's served by a separate Builder (main.kv).
Any help would be appreciated! Been struggling with this clock for a few hours now. The trouble seems to be on the .kv side from what I can tell.
BONUS: If you want to go the extra mile, I also want to add a timer that counts down x amount on press of a button on the .kv. The x amount would be different depending on which button you press.
I have made a fork of the original kivydigitalclock here. It should be easier to use than the original. You can add it to your .kv file just as any other widget. For example, something like:
<ContScreen>:
FloatLayout
size_hint: .1,.1
canvas.before:
Color:
rgba: 0,0,0,1
Rectangle:
pos: self.pos
size: self.size
Label:
text: app.time
DigitalClock:
pos_hint: {'right': 1.0, 'center_y': 0.5}
size_hint: (0.2, 0.2)
should work. Note that in your main .py file you will need to include:
from digitalclock.digitalclock import DigitalClock
(assuming that the digitalclock.py file is in a digitalclock folder)
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.