Kivy 自定义形状按钮仍然是矩形小部件

问题描述 投票:0回答:1

我正在尝试创建一些相互堆叠的不规则矩形按钮。我已经完成了大部分任务,代码如下,代码创建并保存各种形状的 .png,然后在 kivy 应用程序中使用这些形状。我按照我想要的方式调整了大小和位置,但我遇到的问题是图像小部件似乎是矩形的,因此即使形状不重叠,小部件也会重叠。由此产生的问题是 on_press() 函数并不总是触发正确的按钮,尤其是在边缘附近,如此处的图像所示。 (屏幕截图上的红色框是手绘的,以显示小部件的形状!)

我想要实现的想法是,按下形状内部将激活按钮。有什么想法可以将面积缩小到形状内部吗?

谢谢

from PIL import Image, ImageDraw
from kivy.app import App
from kivy.uix.image import Image as Img
from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.floatlayout import FloatLayout


def calculate_hw(tuple_list, ht = False, wt = False):
    'using the coordinates of the shapes, returns the height and/or width'

    if not tuple_list:
        return None
    
    min_x = min(tuple_list, key=lambda tup: tup[0])[0]
    max_x = max(tuple_list, key=lambda tup: tup[0])[0]
    
    min_y = min(tuple_list, key=lambda tup: tup[1])[1]
    max_y = max(tuple_list, key=lambda tup: tup[1])[1]
    
    h = max_y - min_y
    w = max_x - min_x
    
    if ht and wt:
        return h, w
    elif ht:
        return h
    elif wt:
        return w

def calculate_y_difference(tuple_list):
    '''
    returns the height of the objecy at its widest part
    used to calculate the value of the pos_y of the next button
    '''

    sorted_by_x = sorted(tuple_list, key=lambda tup: tup[0], reverse=True)    
    y_diff = sorted_by_x[0][1] - sorted_by_x[1][1]    
    return abs(y_diff)

shapes = {
    '1': [(500.0, 0.0), (500.0, 93.38888888888945), (0.0, 25.0), (0.0, 0.0), (500.0, 0.0)], 
    '2': [(500.0, 93.38888888888945), (500.0, 133.33333333333357), (0.0, 75.0), (0.0, 25.0), (500.0, 93.38888888888945)], 
    '3': [(500.0, 133.33333333333357), (500.0, 188.3333333333333), (0.0, 125.0), (0.0, 75.0), (500.0, 133.33333333333357)]}

shapes_height = {k: calculate_hw(shapes[k], ht = True) for k in shapes.keys() }
shape_pos_height = {k: calculate_y_difference(shapes[k]) for k in shapes.keys() }
shape_width = {k: calculate_hw(shapes[k], wt = True) for k in shapes.keys() }

#Loop through each shape, create it, crop it and save it as a .png
for shape_name, vertices in shapes.items():
    # Create a new transparent image
    width, height = 4000, 4000
    background_color = (0, 0, 0, 0)  # Transparent background

    image_normal = Image.new("RGBA", (width, height), background_color)
    image_down = Image.new("RGBA", (width, height), background_color)

    # Create a drawing object
    draw_normal = ImageDraw.Draw(image_normal)
    draw_down = ImageDraw.Draw(image_down)

    # Draw the irregular shape
    draw_normal.polygon(vertices, ) #fill=(255, 0, 0, 128))  # Fill color with transparency
    draw_down.polygon(vertices, fill=(0, 255, 0, 128))  # Fill color with transparency

    # Find the bounding box of the polygon
    min_x = min(point[0] for point in vertices)
    min_y = min(point[1] for point in vertices)
    max_x = max(point[0] for point in vertices)
    max_y = max(point[1] for point in vertices)

    #crop the image
    image_normal = image_normal.crop((min_x, min_y, max_x, max_y))
    image_down = image_down.crop((min_x, min_y, max_x, max_y))

    # Save the image as a PNG file
    image_normal.save(f"{shape_name}.png", "PNG", dpi=(2000, 2000), resolution_unit="in")
    image_down.save(f"{shape_name}_down.png", "PNG", dpi=(2000, 2000), resolution_unit="in")


class MyButton(ButtonBehavior, Img):
    '''
    a very basic button with an image used instead of a Label
    '''
    def __init__(self,normal, down, **kwargs):
        super(MyButton, self).__init__(**kwargs)

        self.normal = normal
        self.down = down
        self.source = self.normal

    def on_press(self):
        self.source = self.down
    def on_release(self):
        self.source = self.normal


class Main(FloatLayout):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        
        y = 300    
        for shape in shapes:                #Loop through the shape names
            y -= shape_pos_height[shape]    #the y pos of the first shape, this is reduced by the width of the shape to achieve a stacking effect
            
            #add the custom budget
            self.add_widget(MyButton(
                down = f'{shape}_down.png', 
                normal = f'{shape}.png', 
                size_hint = (None, None),
                size = (shape_width[shape], shapes_height[shape]),
                pos = (100,y)  ))


class MainApp(App):
    def build(self):
        return Main()
        

MainApp().run()
python button kivy
1个回答
0
投票

您需要创建自己的函数来测试碰撞,而不是依赖 ButtonBehavior。这是一个使用圆的完整示例。我还添加了创建和调度 on_release 和 on_press 事件。

from kivy.app import App
from kivy.lang import Builder
from kivy.properties import ColorProperty, NumericProperty
from kivy.uix.label import Label

kv = """
<CircleButton>:
    size_hint: None, None
    size: self.radius * 2, self.radius * 2
    canvas.before:
        Color:
            rgba: self._button_color
        Ellipse:
            size: self.size
            pos: self.pos

AnchorLayout:
    CircleButton:
        text: 'Push'
        radius: dp(75)
        on_press: print('Button Pressed')
        on_release: print('Button Released')
"""


class CircleButton(Label):
    radius = NumericProperty(50)
    normal_color = ColorProperty('gray')
    down_color = ColorProperty('blue')
    _button_color = ColorProperty('gray')

    def __init__(self, **kwargs):
        self.register_event_type('on_release')
        self.register_event_type('on_press')
        super().__init__(**kwargs)

    def on_press(self):
        pass

    def on_release(self):
        pass

    def is_inside_circle(self, touch_x, touch_y):
        dx = abs(touch_x - self.center_x)
        dy = abs(touch_y - self.center_y)
        return dx ** 2 + dy ** 2 <= self.radius ** 2

    def on_touch_down(self, touch):
        if self.is_inside_circle(*touch.pos):
            touch.grab(self)
            self._button_color = self.down_color
            self.dispatch('on_press')
            return True
        super().on_touch_down(touch)

    def on_touch_up(self, touch):
        if touch.grab_current is self:
            self._button_color = self.normal_color
            self.dispatch('on_release')
            return True
        return super().on_touch_up(touch)


class CircleButtonExampleApp(App):
    def build(self):
        return Builder.load_string(kv)


CircleButtonExampleApp().run()
© www.soinside.com 2019 - 2024. All rights reserved.