Search code examples
pythonandroiddrag-and-dropkivykivy-language

Image that disappears when dragging it to a target on Kivy


I am developing a game in which users must match images by their initial letter (in Spanish), so that when they drag to a point (the cauldron) an image that begins with the correct letter (in this case the igloo, the Indian and the magnet) this image disappears.Example screen

In other words, basically, an image disappears when dragged to a specific point.

*.kv

#:import win kivy.core.window

<Picture@Scatter>:
    source: None
    on_size: self.center = win.Window.center
    size: image.size
    size_hint: None, None
    do_scale: False
    do_rotation: False

    Image:
        id: image
        source: root.source
        size: 250, 250 / self.image_ratio

<Relaciona3x2>:
    AnchorLayout:
        Image:
            source: 'data/img/fondobosque.jpg'
            allow_stretch: True
            keep_ratio: False

        FloatLayout:
            size_hint: 1, 1

            Image:
                id: img003
                source: 'data/img/caldero.png'
                size_hint: 0.55, 0.55
                pos_hint: {"center_x": 0.5, "center_y": 0.20}

            Button:
                size_hint:.06, 0.1
                text: "Volver al menú"
                on_release: app.root.current = 'menu'

            Picture:
                id: potionscatter
                source: "data/img/letra_i/iglú.png"
                pos: 175, 680
            Picture:
                source: "data/img/letra_i/indio.png"
                pos: 835, 680
            Picture:
                source: "data/img/letra_m/moto.png"
                pos: 1495, 680
            Picture:
                source: "data/img/letra_u/uña.png"
                pos: 175, 420
            Picture:
                source: "data/img/letra_i/imán_1.png"
                pos: 835, 420
            Picture:
                source: "data/img/letra_u/urraca.png"
                pos: 1495, 420

<Relaciona4x2Screen>:
    AnchorLayout:
        Image:
            source: 'data/img/fondobosque.jpg'
            allow_stretch: True
            keep_ratio: False

        FloatLayout:
            size_hint: 1, 1

            Image:
                id: img003
                source: 'data/img/caldero.png'
                size_hint: 0.55, 0.55
                pos_hint: {"center_x": 0.5, "center_y": 0.20}

            Button:
                size_hint:.06, 0.1
                text: "Volver al menú"
                on_release: app.root.current = 'menu'

<Relaciona5x2Screen>:
    AnchorLayout:
        Image:
            source: 'data/img/fondobosque.jpg'
            allow_stretch: True
            keep_ratio: False

        FloatLayout:
            size_hint: 1, 1

            Image:
                id: img003
                source: 'data/img/caldero.png'
                size_hint: 0.55, 0.55
                pos_hint: {"center_x": 0.5, "center_y": 0.20}

            Button:
                size_hint:.06, 0.1
                text: "Volver al menú"
                on_release: app.root.current = 'menu'

relaciona.py

__all__ = ('Relaciona3x2', 'Relaciona4x2', 'Relaciona5x2')

import kivy
kivy.require('1.0.6')

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen

Builder.load_file('relaciona.kv')

class Relaciona3x2(Screen):
    pass

class Relaciona4x2(Screen):
    pass

class Relaciona5x2(Screen):
    pass

main.py

import kivy
kivy.require('1.0.6')

from kivy.uix.popup import Popup
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.animation import Animation
from kivy.uix.button import Button
from kivy.uix.scatter import Scatter
from kivy.logger import Logger
from kivy.uix.gesturesurface import GestureSurface

from letras import DrawGame, ImageEx, SoundEx, ContainerBox
from explota import BubblePop, BubbleGame
from relaciona import *
from borra import Borra
from caza import Caza
from config import Config
from kivy.core.window import Window

Builder.load_file('design.kv')

class MyScreenManager(ScreenManager):
    def __init__(self):
        super (MyScreenManager, self).__init__()
        
class HomeMenu(Screen):
    pass

class LetrasScreen(Screen):
    pass

class ExplotaScreen(Screen):
    pass

class Relaciona3x2Screen(Screen):
    pass

class Relaciona4x2Screen(Screen):
    pass

class Relaciona5x2Screen(Screen):
    pass

class BorraScreen(Screen):
    pass

class CazaScreen(Screen):
    pass

class ConfigScreen(Screen):
    pass

class myApp(App):
    def build(self):
        Window.fullscreen = 'auto'
        return MyScreenManager()
    
    def on_pause(self):
        return True
    
    def on_resume(self):
        pass
    
myApp().run()

Solution

  • I have used DragNDropWidget to solve this problem. It's quite simple to use but now I don't know how to change the size of the buttons, I would like them to be bigger and somewhat separated from each other.

    DragNDropWidget.py

    #!/usr/bin/python
    # -*- coding: UTF-8 -*-
    from kivy.core.window import Window
    from kivy.animation import Animation
    import copy
    from kivy.uix.widget import Widget
    from kivy.properties import (
        ListProperty, NumericProperty, BooleanProperty, ObjectProperty)
    
    
    class DragNDropWidget(Widget):
        # let kivy take care of kwargs and get signals for free by using
        # properties
        droppable_zone_objects = ListProperty([])
        bound_zone_objects = ListProperty([])
        drag_opacity = NumericProperty(1.0)
        drop_func = ObjectProperty(None)
        drop_args = ListProperty([])
        remove_on_drag = BooleanProperty(True)
    
        def __init__(self, **kw):
            super(DragNDropWidget, self).__init__(**kw)
    
            self.register_event_type("on_drag_start")
            self.register_event_type("on_being_dragged")
            self.register_event_type("on_drag_finish")
            self.register_event_type("on_motion_over")
            self.register_event_type("on_motion_out")
    
            self._dragged = False
            self._dragable = True
            self._fired_already = False
    
        def set_dragable(self, value):
            self._dragable = value
    
        def set_remove_on_drag(self, value):
            """
            This function sets the property that determines whether the dragged widget is just copied from its parent or taken from its parent
            @param value: either True or False. If True then the widget will disappear from its parent on drag, else the widget will jsut get copied for dragging
            """
            self.remove_on_drag = value
    
        def set_bound_axis_positions(self):
            for obj in self.bound_zone_objects:
                try:
                    if self.max_y < obj.y+obj.size[1]-self.size[1]:
                        self.max_y = obj.y+obj.size[1]-self.size[1]
                except AttributeError:
                    self.max_y = obj.y+obj.size[1]-self.size[1]
                try:
                    if self.max_x < obj.x+obj.size[0]-self.size[0]:
                        self.max_x = obj.x + obj.size[0]-self.size[0]
                except AttributeError:
                    self.max_x = obj.x+obj.size[0]-self.size[0]
                try:
                    if self.min_y > obj.y:
                        self.min_y = obj.y
                except AttributeError:
                    self.min_y = obj.y
                try:
                    if self.min_x > obj.x:
                        self.min_x = obj.x
                except AttributeError:
                    self.min_x = obj.x
    
        def on_touch_down(self, touch):
            if self.collide_point(touch.x, touch.y) and self._dragable:
                # detect if the touch is short - has time and end (if not dispatch drag)
                if abs(touch.time_end - touch.time_start) > 0.2:
                    self.dispatch("on_drag_start")
    
    
        def on_touch_up(self, touch):
            if self._dragable and self._dragged:
                self.short_touch = True
                self.dispatch("on_drag_finish")
                self.short_touch = False
    
        def on_touch_move(self, touch):
            if self._dragged and self._dragable:
                x = touch.x
                y = touch.y
    
                try:
                    if touch.x < self.min_x:
                        x = self.min_x
                    if touch.x > self.max_x:
                        x = self.max_x
                    if touch.y < self.min_y:
                        y = self.min_y
                    if touch.y > self.max_y:
                        y = self.max_y
                except AttributeError:
                    pass
                self.pos = (x, y)
    
        def easy_access_dnd(self, function_to_do, function_to_do_out, arguments = [], bind_functions = []):
            """
            This function enables something that can be used instead of drag n drop
            @param function_to_do: function that is to be called when mouse_over event is fired on the widget
            @param bind_functions: what is really to be done - background function for GUI functionality
            """
            Window.bind(mouse_pos=self.on_motion)
            self.easy_access_dnd_function = function_to_do
            self.easy_access_dnd_function_out = function_to_do_out
            self.easy_access_dnd_function_aguments = arguments
            self.easy_access_dnd_function_binds = bind_functions
    
        def on_motion(self, etype, moutionevent):
            if self.collide_point(Window.mouse_pos[0], Window.mouse_pos[1]):
                if not self._fired_already:
                    self.dispatch("on_motion_over")
            else:
                self.dispatch("on_motion_out")
    
        def on_motion_over(self):
            self.easy_access_dnd_function(
                self.easy_access_dnd_function_aguments,
                self.easy_access_dnd_function_binds)
    
            self._fired_already = True
    
        def on_motion_out(self):
            try:
                self.easy_access_dnd_function_out()
            except AttributeError:
                pass
            self._fired_already = False
    
        def on_drag_start(self):
            print ('drag start')
            self.opacity = self.drag_opacity
            self.set_bound_axis_positions()
            self._old_drag_pos = self.pos
            self._old_parent = self.parent
            self._old_index = self.parent.children.index(self)
            self._dragged = True
            if self.remove_on_drag:
                self.reparent(self)
            else:
                #create copy of object to drag
                self.reparent(self)
                # the final child class MUST implement __deepcopy__
                # IF self.remove_on_drag == False !!! In this case this is
                # met in DragableArhellModelImage class
                copy_of_self = copy.deepcopy(self)
                self._old_parent.add_widget(copy_of_self, index=self._old_index)
    
        def on_drag_finish(self):
            print ('drag finish')
            if self._dragged and self._dragable:
                self.opacity = 1.0
                dropped_ok = False
                for obj in self.droppable_zone_objects:
                    if obj.collide_point(*self.pos):
                        dropped_ok = True
                if dropped_ok:
                    self.drop_func(*self.drop_args)
                    anim = Animation(opacity=0, duration=0.7, t="in_quad")
                    anim.bind(on_complete=self.deparent)
                    anim.start(self)
                else:
                    anim = Animation(pos=self._old_drag_pos, duration=0.7, t="in_quad")
                    if self.remove_on_drag:
                        anim.bind(on_complete = self.reborn)
                    else:
                        anim.bind(on_complete = self.deparent)
                    anim.start(self)
                self._dragged = False
    
        def deparent(self, widget="dumb", anim="dumb2"):
            self.get_root_window().remove_widget(self)
    
        def on_being_dragged(self):
            print ('being dragged')
    
        def reborn(self, widget, anim):
            self.deparent()
            self._old_parent.add_widget(self, index=self._old_index)
    
        def reparent(self, widget):
            parent = widget.parent
            orig_size = widget.size
            if parent:
                parent.remove_widget(widget)
                parent.get_root_window().add_widget(widget)
                widget.size_hint = (None, None)
                widget.size = orig_size
    

    DragableButton.py

    Created on Oct 24, 2012
    
    @author: Pavel Kostelnik
    '''
    
    
    from DragNDropWidget import DragNDropWidget
    from kivy.uix.button import Button
    
    
    class DragableButton(Button, DragNDropWidget):
        '''
        classdocs
        '''
        def __init__(self, **kw):
            '''
            Constructor
            '''
            #Button.__init__(self, **kw)
            super(DragableButton, self).__init__(**kw)
            self.size_hint = (None, None)
    
        def __deepcopy__(self, dumb):
            return DragableButton(text=self.text,
                                  droppable_zone_objects=self.droppable_zone_objects,
                                  bound_zone_objects=self.bound_zone_objects,
                                  drag_opacity=self.drag_opacity,
                                  drop_func=self.drop_func,
                                  remove_on_drag=self.remove_on_drag)
    

    arrastrar.py

    __all__ = ('Relaciona3x2', 'Relaciona4x2', 'Relaciona5x2')
    
    import kivy
    kivy.require('1.0.6')
    
    from DragableButton import DragableButton
    from kivy.app import App
    from kivy.lang import Builder
    from kivy.uix.screenmanager import ScreenManager, Screen
    
    Builder.load_file('arrastrar.kv')
    
    class Relaciona3x2(Screen):
        pass
    
    class Relaciona4x2(Screen):
        pass
    
    class Relaciona5x2(Screen):
        pass
    
    class ArrastraApp(App):
        def build(self):
            #Window.fullscreen = 'auto'
            return Relaciona3x2()
        
        def greet(self):
            print('Draggin done!')
        
    if __name__ == "__main__":
        ArrastraApp().run()
    

    arrastrar.kv

    #:kivy 1.9.0
    
    <Relaciona3x2>:
        canvas.before:
            Rectangle:
                pos: self.pos
                size: self.size
                source: 'data/img/fondobosque.jpg'
        BoxLayout:
            orientation: 'vertical'
            BoxLayout:
                id: from_box
                DragableButton:
                    canvas:
                        Rectangle:
                            pos: self.pos
                            size: self.size
                            source: 'data/img/letra_i/iglu.png'
                    bound_zone_objects: [from_box, to_box ]
                    droppable_zone_objects: [to_box, ]
                    drop_func: app.greet
                    
                DragableButton:
                    canvas:
                        Rectangle:
                            pos: self.pos
                            size: self.size
                            source: 'data/img/letra_i/indio.png'
                    bound_zone_objects: [from_box, to_box, ]
                    droppable_zone_objects: [to_box, ]
                    drop_func: app.greet
    
                    
                DragableButton:
                    canvas:
                        Rectangle:
                            pos: self.pos
                            size: self.size
                            source: 'data/img/letra_m/moto.png'
                    bound_zone_objects: [from_box, to_box, ]
                    #droppable_zone_objects: [to_box, ]
                    drop_func: app.greet
            
            BoxLayout:
                id: from_box
                DragableButton:
                    canvas:
                        Rectangle:
                            pos: self.pos
                            size: self.size
                            source: 'data/img/letra_u/una.png'
                    bound_zone_objects: [from_box, to_box, ]
                    #droppable_zone_objects: [to_box, ]
                    drop_func: app.greet
                    
                DragableButton:
                    canvas:
                        Rectangle:
                            pos: self.pos
                            size: self.size
                            source: 'data/img/letra_i/iman_1.png'
                    bound_zone_objects: [from_box, to_box, ]
                    droppable_zone_objects: [to_box, ]
                    drop_func: app.greet
                    
                DragableButton:
                    canvas:
                        Rectangle:
                            pos: self.pos
                            size: self.size
                            source: 'data/img/letra_u/urraca.png'
                    bound_zone_objects: [from_box, to_box, ]
                    #droppable_zone_objects: [to_box, ]
                    drop_func: app.greet
    
            Image:
                id: to_box
                source: "data/img/caldero.png"
                
    <Relaciona4x2>:
        AnchorLayout:
            Image:
                source: "data/img/fondobosque.jpg"
                allow_stretch: True
                keep_ratio: False
                
    <Relaciona5x2>:
        AnchorLayout:
            Image:
                source: "data/img/fondobosque.jpg"
                allow_stretch: True
                keep_ratio: False