在 Kivy 中,如何制作可点击且(按比例)调整大小的位图?

发布于 2025-01-13 00:42:29 字数 3730 浏览 0 评论 0原文

我试图在 Kivy 中显示位图,并赋予它几个属性,其中一些属性似乎是互斥的。它应该是:

  • 可点点击(能够找到点坐标,而不仅仅是单击按钮)
  • 可调整大小 调整
  • 大小时保持比例

这是最后一个似乎是问题的项目。我可以制作一个基本上是按钮类型对象的位图,并且它是可点击的 - 但它是固定大小的。或者我可以制作一个“弹性”位图,它会在窗口大小调整时调整大小。但是 - 要么是比例变化,要么是小部件的“位图”部分以正确的比例显示,但小部件有一个不成比例的组件,可以单击。

第一组是“按钮”模式,第二组是“拉伸”模式。

button-模式小部件

stretch-mode widget

在拉伸模式下,实际显示位图都会调整大小,并保持比例大小。但是,显示它的图像小部件正在不按比例调整大小。 (因此 keep_ratio 选项适用于显示的位图,但不适用于实际的 Widget 对象。)这很好,但整个 Widget 都是可点击的,而且我还没有找到一种方法确定单击点相对于位图的位置。

我可以想到半解决方案,例如强制窗口大小按比例调整,根据需要更改宽度/高度以进行匹配,但这相当混乱。似乎应该有某种方法可以使用 Kivy 对象/属性来做到这一点,但我还没有找到。

实际代码示例。要以“按钮”模式运行,

  KIVY_NO_ARGS=1 python show_bitmap.py -b
import sys
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.image import Image
from kivy.graphics import Color, Rectangle
from kivy.core.window import Window
from kivy.uix.relativelayout import RelativeLayout
hex_bmp = 'basic_hexes.png'
hex_bmp_size = (456, 292)

stretch_params = {'source': hex_bmp,  # Bitmap is stretchable, but click-region is wrong
                  'allow_stretch': True,
                  'keep_ratio': True,
                  'pos': (110, 110),
                  'size': hex_bmp_size,
                  'size_hint': (0.5, 0.5),
                  }
button_params = {'source': hex_bmp,  # Behaves like a Button, with regard to click-region
                 'allow_stretch': True,
                 'keep_ratio': True,
                 'pos': (110, 110),
                 'size': hex_bmp_size,
                 'size_hint': (None, None),
                 }
new_params = stretch_params

class VariableImage(Image):
    def __init__(self, **kwargs):
        super().__init__(**new_params, **kwargs)
        Window.bind(on_resize=self.on_window_resize)
        with self.canvas.before:
            Color(0.9, 0.2, 0.2, 0.5)
            self.bg_rect = Rectangle(pos=self.pos, size=self.size)

    def on_touch_up(self, touch):
        if self.collide_point(*touch.pos):
            print(f"*** Clicked VariableImage, pos: {touch.pos}")

    def on_window_resize(self, window, width, height):
        self.bg_rect.size = self.size

class TopBoxLayout(RelativeLayout):
    def __init__(self, initial_window_size, **kwargs):
        super().__init__(**kwargs)
        self.orientation = 'vertical'
        self.bg_rect = None
        self.size = initial_window_size
        self.label_2 = Label(text='WTF?', pos_hint={'top': 1}, size_hint=(1.0, 0.1))
        self.add_widget(self.label_2)
        self.back_picture = VariableImage()
        self.add_widget(self.back_picture)
        self.setup_background()
        Window.bind(on_resize=self.on_window_resize)

    def setup_background(self):
        with self.canvas.before:
            Color(0.6, 0.6, 0.6, 0.9)
            self.bg_rect = Rectangle(pos=self.pos, size=self.size)
        with self.canvas.after:
            Color(0.9, 0.9, 0.9, 0.9)
            self.target_rect = Rectangle(pos=(100, 100), size=(10, 10))

    def on_window_resize(self, window, width, height):
        self.bg_rect.pos = self.pos
        self.bg_rect.size = self.size

class canvasMain(App):
    def build(self):
        Window.size = (700, 450)
        self.root = TopBoxLayout(Window.size)
        return self.root

if __name__ == '__main__':
    if len(sys.argv) > 1 and sys.argv[1] == '-b':
        new_params = button_params
    canvasMain().run()

I'm trying to display a bitmap in Kivy, and give it several properties, some of which seem to be mutually-exclusive. It should be:

  • point-clickable (able to find point-coordinates, not just a button-click)
  • resizable
  • maintain proportion when resized

It's the last item that seems to be a problem. I can make a bitmap that is basically a Button-type object, and it's point-clickable - but it's a fixed size. Or I can make a "stretchy" bitmap, which will resize when the Window resizes. But - either it's proportion shifts, or the "bitmap" section of the Widget display in correct proportion, but there's a non-proportional component to the Widget, that can be clicked.

The first set is "button" mode, the second is "stretch" mode.

button-mode widget

stretch-mode widget

In the stretch-mode, the actual display bitmap does both resize, and maintain a proportional size. However, the Image Widget that displays it, is resizing non-proportionally. (So the keep_ratio option, applies to the bitmap that is displayed, but not the actual Widget object.) Which would be fine, but the entire Widget is clickable, and I haven't found a way to determine where a clicked point is, relative to the bitmap.

I can think of semi-solutions, like forcing Window resizes to be proportional, changing width/height as needed to match, but that's fairly messy. It seems like there should be some way of doing it, using Kivy objects/properties, but I haven't found one yet.

Actual code sample. To run in "button" mode,

  KIVY_NO_ARGS=1 python show_bitmap.py -b
import sys
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.image import Image
from kivy.graphics import Color, Rectangle
from kivy.core.window import Window
from kivy.uix.relativelayout import RelativeLayout
hex_bmp = 'basic_hexes.png'
hex_bmp_size = (456, 292)

stretch_params = {'source': hex_bmp,  # Bitmap is stretchable, but click-region is wrong
                  'allow_stretch': True,
                  'keep_ratio': True,
                  'pos': (110, 110),
                  'size': hex_bmp_size,
                  'size_hint': (0.5, 0.5),
                  }
button_params = {'source': hex_bmp,  # Behaves like a Button, with regard to click-region
                 'allow_stretch': True,
                 'keep_ratio': True,
                 'pos': (110, 110),
                 'size': hex_bmp_size,
                 'size_hint': (None, None),
                 }
new_params = stretch_params

class VariableImage(Image):
    def __init__(self, **kwargs):
        super().__init__(**new_params, **kwargs)
        Window.bind(on_resize=self.on_window_resize)
        with self.canvas.before:
            Color(0.9, 0.2, 0.2, 0.5)
            self.bg_rect = Rectangle(pos=self.pos, size=self.size)

    def on_touch_up(self, touch):
        if self.collide_point(*touch.pos):
            print(f"*** Clicked VariableImage, pos: {touch.pos}")

    def on_window_resize(self, window, width, height):
        self.bg_rect.size = self.size

class TopBoxLayout(RelativeLayout):
    def __init__(self, initial_window_size, **kwargs):
        super().__init__(**kwargs)
        self.orientation = 'vertical'
        self.bg_rect = None
        self.size = initial_window_size
        self.label_2 = Label(text='WTF?', pos_hint={'top': 1}, size_hint=(1.0, 0.1))
        self.add_widget(self.label_2)
        self.back_picture = VariableImage()
        self.add_widget(self.back_picture)
        self.setup_background()
        Window.bind(on_resize=self.on_window_resize)

    def setup_background(self):
        with self.canvas.before:
            Color(0.6, 0.6, 0.6, 0.9)
            self.bg_rect = Rectangle(pos=self.pos, size=self.size)
        with self.canvas.after:
            Color(0.9, 0.9, 0.9, 0.9)
            self.target_rect = Rectangle(pos=(100, 100), size=(10, 10))

    def on_window_resize(self, window, width, height):
        self.bg_rect.pos = self.pos
        self.bg_rect.size = self.size

class canvasMain(App):
    def build(self):
        Window.size = (700, 450)
        self.root = TopBoxLayout(Window.size)
        return self.root

if __name__ == '__main__':
    if len(sys.argv) > 1 and sys.argv[1] == '-b':
        new_params = button_params
    canvasMain().run()

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

韶华倾负 2025-01-20 00:42:29

我认为关键是确保 VariableImage 实例保持其比例与位图的比例相匹配。这是执行此操作的代码的修改版本:

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.image import Image
from kivy.core.window import Window
from kivy.uix.relativelayout import RelativeLayout

kv = '''
<TopBoxLayout>:
    canvas.before:
        Color:
            rgba: (0.6, 0.6, 0.6, 0.9)
        Rectangle:
            pos: self.pos
            size: self.size
        Color:
            rgba: (0.9, 0.9, 0.9, 0.9)
        Rectangle:
            pos: 100, 100
            size: 10, 10
    Label:
        text: 'WTF?'
        pos_hint: {'top': 1}
        size_hint: (1.0, 0.1)
    VariableImage:
        id: vi
        size_hint: None, None
        # size: 0.5 * root.width, 0.5 * root.width / self.image_ratio  # does the same as the `on_size()` method
<VariableImage>:
    source: 'basic_hexes.png'
    allow_stretch: True
    keep_ratio: True
    pos: (110, 110)
    canvas.before:
        Color:
            rgba: (0.9, 0.2, 0.2, 0.5)
        Rectangle:
            pos: self.pos
            size: self.size
'''

class VariableImage(Image):

    def on_touch_up(self, touch):
        if self.collide_point(*touch.pos):
            print(f"*** Clicked VariableImage, pos: {touch.pos}")

class TopBoxLayout(RelativeLayout):
    def on_size(self, instance, new_size):
        vi = self.ids.vi
        # adjust size of VariableImage based on layout width
        # a more complicated logic can be used, like keeping size with layout size
        vi.size = 0.5 * self.width, 0.5 * self.width / vi.image_ratio

class canvasMain(App):
    def build(self):
        Window.size = (700, 450)
        Builder.load_string(kv)
        return TopBoxLayout()

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

它使用 kivy 语言进行一些简化。 kv 中的行:

size: 0.5 * root.width, 0.5 * root.width / self.image_ratio

保持 VariableImage 的比率与位图比率相同(如果未注释)。 TopBoxLayouton_size() 方法将执行完全相同的操作,但更容易用于更复杂的逻辑。

您不需要这两种方法。可以删除其中之一。

I think the key is to make sure that the VariableImage instance keeps its ratio matching the ratio of the bitmap. Here is a modified version of your code that does that:

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.image import Image
from kivy.core.window import Window
from kivy.uix.relativelayout import RelativeLayout

kv = '''
<TopBoxLayout>:
    canvas.before:
        Color:
            rgba: (0.6, 0.6, 0.6, 0.9)
        Rectangle:
            pos: self.pos
            size: self.size
        Color:
            rgba: (0.9, 0.9, 0.9, 0.9)
        Rectangle:
            pos: 100, 100
            size: 10, 10
    Label:
        text: 'WTF?'
        pos_hint: {'top': 1}
        size_hint: (1.0, 0.1)
    VariableImage:
        id: vi
        size_hint: None, None
        # size: 0.5 * root.width, 0.5 * root.width / self.image_ratio  # does the same as the `on_size()` method
<VariableImage>:
    source: 'basic_hexes.png'
    allow_stretch: True
    keep_ratio: True
    pos: (110, 110)
    canvas.before:
        Color:
            rgba: (0.9, 0.2, 0.2, 0.5)
        Rectangle:
            pos: self.pos
            size: self.size
'''

class VariableImage(Image):

    def on_touch_up(self, touch):
        if self.collide_point(*touch.pos):
            print(f"*** Clicked VariableImage, pos: {touch.pos}")

class TopBoxLayout(RelativeLayout):
    def on_size(self, instance, new_size):
        vi = self.ids.vi
        # adjust size of VariableImage based on layout width
        # a more complicated logic can be used, like keeping size with layout size
        vi.size = 0.5 * self.width, 0.5 * self.width / vi.image_ratio

class canvasMain(App):
    def build(self):
        Window.size = (700, 450)
        Builder.load_string(kv)
        return TopBoxLayout()

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

This uses the kivy language to do some simplification. The line in the kv:

size: 0.5 * root.width, 0.5 * root.width / self.image_ratio

keeps the ratio of the VariableImage the same as the bitmap ratio (if uncommented). The on_size() method of TopBoxLayout will do exactly the same thing, but is easier to use for more complicated logic.

You don't need both approaches. One or the other can be deleted.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文