python - 如何在 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()
解决方案
这可能是一个复杂的问题。处理小部件时要记住的一件事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)
推荐阅读
- android - 如何使用匕首 2 在 Activity 类中注入具有接口作为其构造函数参数的 Presenter 类?
- ffmpeg - ffmpeg 通过 rtp 流式传输 RAW TS
- c++ - 在 C++ 中读取配置文件 json 并没有停止
- android - 使用 adb 监控应用程序的内存使用情况
- javascript - ul list .remove() 之后的jQuery不会再次填充列表
- php - 当我在 mysql 中保存数据时出现异常
- asp.net - ASP.NET 条件是/否消息框
- json - 从远程 JSON 文件归档 Bootstrap 数据表
- node.js - 快递:req.params 与 req.body (JSON)
- typescript - 任何关键字的缺点