首页 > 解决方案 > 如何在 Kivy 中将可拖动图像保持在另一个图像的矩形边界内?

问题描述

我有一个棋盘图像,其中有一个可拖动的白色皇后棋子图像,我希望拖动棋子永远不会超过棋盘图像的边界。

我查看了文档,但仍然看不到方法。有人(约翰安德森)可以告诉我如何做到这一点吗?

这是代码:

import kivy
from kivy.app import App
from kivy.uix.behaviors import DragBehavior
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.relativelayout import RelativeLayout
from kivy.uix.splitter import Splitter
from kivy.graphics import Color, Rectangle
from kivy.uix.image import Image

kivy.require('2.0.0')

class MoveableImage(DragBehavior, Image):

    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]

        self.size_hint = (.13, .13)  
        self.keep_ratio = True
        self.allow_stretch = True  

    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]


class SplitBottom(Splitter):
    def __init__(self, **kwargs):
        super(SplitBottom, self).__init__(**kwargs)

        self.sizable_from = 'bottom'
        self.min_size = 200
        self.max_size = 1100 
        self.size_hint = (1, .8)  

    def on_release(self, *args):
        print('Released split1_bottom bar')
        print('Y value = %d' % self.y)


class SplitLeft(Splitter):
    def __init__(self, **kwargs):
        super(SplitLeft, self).__init__(**kwargs)

        self.sizable_from = 'left'
        self.min_size = 74
        self.max_size = 1437.2 
        self.size_hint = (3.36, 1) 

    def on_release(self, *args):
        print('Released split2_left bar')
        print('X value = %d' % self.x)


# Holds the chessboard image
class ChessboardGuiBoxlayoutVert(BoxLayout):
    def __init__(self, **kwargs):
        super(ChessboardGuiBoxlayoutVert, self).__init__(**kwargs)

        self.orientation = 'vertical'
        self.spacing = 40
        self.chessboard_image_rect = Rectangle(size=(426.8, 426.8),
                                               pos=(0.0, 60.0))  

    def on_size(self, *args):
        print("RESIZE HAPPENED in class ChessboardGuiBoxlayoutVert")

        print('self.ids.main_image.norm_image_size = (%f, %f)' %
              (self.ids.chessboard_image.norm_image_size[0], self.ids.chessboard_image.norm_image_size[1]))

        print('Size of chessboard_image = (%f, %f)' %
              (round(self.ids.chessboard_image.norm_image_size[0], 1),
               round(self.ids.chessboard_image.norm_image_size[1], 1)))  

        # tracks position of image when resized
        print('Position of chessboard_image = (%f, %f)' %
              (round(self.ids.chessboard_image.center_x - self.ids.chessboard_image.norm_image_size[0] / 2., 1),
               round(self.ids.chessboard_image.center_y - self.ids.chessboard_image.norm_image_size[1] / 2., 1)))


class ChessBoardWidgetRelative(RelativeLayout):
    def __init__(self, **kwargs):
        super(ChessBoardWidgetRelative, self).__init__(**kwargs)

        self.pieces_not_displayed_yet = True
        self.first_touch = True
        self.white_queen_image = None
        self.rect = Rectangle(size=(100, 100),
                              pos=(0.0, 60.0))

        self.repertoire_boxlayout_vert = BoxLayout(orientation='vertical', size_hint_y=.05,
                                                   pos_hint={'center_x': .774})  

        # Padding between layout box and children: [padding_left, padding_top, padding_right, padding_bottom]
        # Padding puts space between widgets and the edge of layout holding the widgets
        # Spacing puts space between the widgets inside a layout
        self.repertoire_boxlayout_horz = BoxLayout(orientation='horizontal', size_hint=(.45, None),
                                                   spacing=10, padding=[0, 55, 0, 15])

        self.white_button = Button(text='White', size_hint=(.04, 1), color=(0, 0, 0, 1),
                                   background_normal='', background_color=(128 / 255, 128 / 255, 128 / 255, 1.0),
                                   bold=True)  # grey
        self.white_button.bind(on_press=self.button_press_event_handler_for_all_buttons)
        self.black_button = Button(text='Black', size_hint=(.04, 1), color=(0, 0, 0, 1),
                                   background_normal='', background_color=(128 / 255, 128 / 255, 128 / 255, 1.0),
                                   bold=True)
        self.black_button.bind(on_press=self.button_press_event_handler_for_all_buttons)

        self.repertoire_boxlayout_horz.add_widget(Label(text='Repertoire for:', size_hint=(.08, 1)))

        self.repertoire_boxlayout_horz.add_widget(self.white_button)
        self.repertoire_boxlayout_horz.add_widget(self.black_button)

        self.repertoire_boxlayout_vert.add_widget(self.repertoire_boxlayout_horz)

        self.chessboard_gui_boxlayout_vert = ChessboardGuiBoxlayoutVert()
        print('chessboard image rect from chessboard_gui_boxlayout_vert = (%f, %f, %f, %f)' %
              (round(self.chessboard_gui_boxlayout_vert.chessboard_image_rect.pos[0], 1),
               round(self.chessboard_gui_boxlayout_vert.chessboard_image_rect.pos[1], 1),
               round(self.chessboard_gui_boxlayout_vert.chessboard_image_rect.size[0], 1),
               round(self.chessboard_gui_boxlayout_vert.chessboard_image_rect.size[1], 1)))

        self.chessboard_gui_boxlayout_vert.ids['white_button'] = self.white_button

        # default size_hint of (1,1) claims all of remaining height
        self.chessboard_image = Image(source="./data/images/chess-pieces/DarkerGreenGreyChessBoard.png", pos=self.pos,
                                      keep_ratio=True, allow_stretch=True)
        self.chessboard_gui_boxlayout_vert.ids['chessboard_image'] = self.chessboard_image

        self.chessboard_gui_boxlayout_vert.add_widget(self.chessboard_image)

        self.chessboard_gui_boxlayout_vert.add_widget(self.repertoire_boxlayout_vert)
        self.add_widget(self.chessboard_gui_boxlayout_vert)

        def update_rect(instance, value):
            instance.rect.pos = instance.pos
            instance.rect.size = instance.size
            if self.pieces_not_displayed_yet:
                test_rect = Rectangle(size=(80, 80), pos=(0.0, 58.0))  
                self.white_queen_image = MoveableImage(source="./data/images/chess-pieces/WhiteQueen57.png",
                                                       pos=test_rect.pos)
                # self.add_widget(
                #     MoveableImage(source="./data/images/chess-pieces/WhiteQueen57.png",
                #                   pos=test_rect.pos))
                self.add_widget(self.white_queen_image)

                self.pieces_not_displayed_yet = False

        self.bind(pos=update_rect, size=update_rect)

    def button_press_event_handler_for_all_buttons(self, button_instance):
        print('The button <%s> is being pressed' % button_instance.text)

    def on_touch_move(self, touch):  
        print('Piece moved!!!')
        if self.first_touch:
            print('FIRST TOUCH Mouse coords = ', str(touch.pos))
            self.first_touch = False
        else:
            print('Mouse coords = ', str(touch.pos))  

    def on_touch_up(self, touch):
        print('ON_TOUCH_UP Mouse coords = ', str(touch.pos))

# region CURRENT LAYOUT HEIRARCHY
''' CURRENT LAYOUT HEIRARCHY
SplitterGuiBoxLayoutHorz  (root of tree)
    split1_boxlayout_vert 
        split1_bottom 
            chessboard_widget_relative
                chessboard_gui_boxlayout_vert
                    chessboard_image
                    repertoire_boxlayout_vert (supports X axis positioning of repertoire_boxlayout_horz)
                        repertoire_boxlayout_horz
                            repertoire_label
                            white_rep_button
                            black_rep_button
        s3_button
    split2 (left bar) 
        s2_button       
'''
# endregion


class SplitterGuiBoxLayoutHorz(BoxLayout):  # root
    def __init__(self, **kwargs):
        super(SplitterGuiBoxLayoutHorz, self).__init__(**kwargs)
        self.orientation = 'horizontal'

        # Splitter 1
        split1_boxlayout_vert = BoxLayout(orientation='vertical')
        # split1_bottom = Splitter(sizable_from='bottom', min_size=74,
        #                          max_size=1100, size_hint=(1, .8))  

        split1_bottom = SplitBottom()

        # region Create ChessBoardWidgetRelative
        chessboard_widget_relative = ChessBoardWidgetRelative()
        # endregion

        split1_bottom.add_widget(chessboard_widget_relative)
        split1_boxlayout_vert.add_widget(split1_bottom)
        s3_button = Button(text='s3', size_hint=(1, 1), background_normal='', background_color=(0, 0.1, 0.2, 1.0))
        split1_boxlayout_vert.add_widget(s3_button)
        self.add_widget(split1_boxlayout_vert)

        split2_left = SplitLeft()
        s2_button = Button(text='s2', size_hint=(.1, 1), background_normal='', background_color=(0, 0.1, 0.2, 1.0))

        split2_left.add_widget(s2_button)
        self.add_widget(split2_left)


class ChessBoxApp(App):
    def build(self):
        return SplitterGuiBoxLayoutHorz()  # root


if __name__ == '__main__':
    ChessBoxApp().run()

标签: pythonkivy

解决方案


这可能是一个复杂的问题。处理小部件时要记住的一件事Image是,您看到的图片大小可能与实际Image小部件的大小不同。当您使用keep_ratio: True. 所以,做你想做的事情的一种方法是拦截on_touch_move()执行拖动的方法并调整touch事件以保持MoveableImage在另一个范围内Image。这是您的修改版本MoveableImage

class MoveableImage(DragBehavior, Image):
    limit_image = ObjectProperty(None)  # keep within this Image

    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]

        self.size_hint = (.13, .13)
        self.keep_ratio = True
        self.allow_stretch = True

    def on_touch_move(self, touch):
        if touch.grab_current is self:
            self.inside_limit_image(touch)  # keep this MoveableImage within the limit_image
        return super(MoveableImage, self).on_touch_move(touch)

    def inside_limit_image(self, touch):
        if self.limit_image is None:
            return

        # calculate limits of actual picture inside this MoveableImage
        m_image_min_x = self.x + (self.width - self.norm_image_size[0])/2.
        m_image_min_y = self.y + (self.height - self.norm_image_size[1])/2.
        m_image_max_x = m_image_min_x + self.norm_image_size[0]
        m_image_max_y = m_image_min_y + self.norm_image_size[1]

        # calculate where limits of picture in the MoveableImage would be if move is allowed
        new_min = [m_image_min_x + touch.dx, m_image_min_y + touch.dy]
        new_max = [new_min[0] + self.norm_image_size[0], new_min[1] + self.norm_image_size[1]]

        # calculate limits of picture in the limit_image
        image_min_x = self.limit_image.x + (self.limit_image.width - self.limit_image.norm_image_size[0]) / 2.
        image_min_y = self.limit_image.y + (self.limit_image.height - self.limit_image.norm_image_size[1]) / 2.
        image_max_x = image_min_x + self.limit_image.norm_image_size[0]
        image_max_y = image_min_y + self.limit_image.norm_image_size[1]

        # adjust touch, if necessary, to keep MoveableImage within limit_image
        if new_min[0] < image_min_x:
            touch.dx = image_min_x - m_image_min_x
        if new_min[1] < image_min_y:
            touch.dy = image_min_y - m_image_min_y
        if new_max[0] > image_max_x:
            touch.dx = image_max_x - m_image_max_x
        if new_max[1] > image_max_y:
            touch.dy = image_max_y - m_image_max_y
        return

    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]

现在,当您使用 this 时MoveableImage,您应该提供对 的引用 limit_image,如下所示:

self.white_queen_image = MoveableImage(source="/data/images/chess-pieces/WhiteQueen57.png", limit_image=self.chessboard_image, pos=test_rect.pos)

推荐阅读