glooey 按钮 (pyglet) 上的图像有时绘制有时不绘制

Images on glooey buttons (pyglet) sometimes drawn sometimes not

我有一个 pyglet 主窗口,我想在上面添加一些控制按钮。每个按钮上都绘制了一个图标。

这是我的简约示例:

from PIL import Image
import glooey
import pyglet

class BasicButton(glooey.Button):
    def __init__(self, *, button_text, image_path):
        super().__init__(button_text)
        self.sprite = None
        self.image_path = image_path

    def do_draw(self):
        image_width, image_height = Image.open(self.image_path).size
        x = self.rect.left + self.rect.width/2 - image_width/2
        y = self.rect.bottom + self.rect.height/2 - image_height/2
        if self.sprite is None:
            self.sprite = pyglet.sprite.Sprite(img=pyglet.image.load(image_path), 
                                               x=x, y=y, batch=self.batch, group=self.group)
        else:
            self.sprite.x = x
            self.sprite.y = y

class AddWidgetsToWindow(glooey.Widget):
    def __init__(self, pyglet_window: pyglet.window.Window):
        super().__init__()
        self.gui = glooey.Gui(pyglet_window)

        vbox = glooey.VBox(default_cell_size=40)
        vbox.padding = 5
        vbox.alignment = 'left'

        menu_buttons = [
            """
            All buttons listed here
            """
        ]

        for button in menu_buttons:
            vbox.add(button)

        self.gui.add(vbox)

window = pyglet.window.Window()
AddWidgetsToWindow(window)
pyglet.app.run()

如果我 运行 这几次,我总是会得到一组不同的正确绘制的按钮:

问题是,为什么会发生这种情况,如何解决?

我不太确定发生了什么,但我有一些意见可能会有所帮助:

  • 这种问题(图片随机不显示)通常是两张图片在同一个OrderedGroup中的标志。这会导致图像随机排序。当应该在后面的图像出现在前面时,看起来应该在前面的图像不存在了。

    在这种情况下,self.sprite 与按钮在同一组中,因此看起来很可疑。

  • 您可能不应该从 Button 继承并实现 do_draw()Button class 设计为使用 class 变量进行配置,如下所示:

    class BasicButton(glooey.Button):
        custom_base_image = pyglet.image.load('...')
        custom_over_image = pyglet.image.load('...')
        custom_down_image = pyglet.image.load('...')
    

    如果您出于某种原因需要实施 do_draw(),但此简单示例中未显示,最好从 Widget 而不是 Button 派生新的小部件。 Button 基本上是 Image/Background/Label 小部件的可配置 Stack。如果我想用这些小部件做一些非常不寻常的事情,我只会从 subclass 重载 Button 方法。但是在这种情况下,do_draw() 似乎根本没有对这些小部件执行任何操作;它只是用图像覆盖它们。如果是这样的话,最好的办法是从 Widget 继承,这样一开始就没有什么可以掩盖的了。请注意,所有小部件(不仅仅是 Button)在被单击时都会发出事件,您可以使用 Rollover 在自定义 Widget subclasses 中轻松实现翻转效果.

  • 这是一件小事,但是用PIL来获取一张图片的宽高有点奇怪。您可以直接从 pyglet 获取此信息,例如:

    img = pyglet.image.load(image_path)
    x = self.rect.center.x - img.width / 2
    y = self.rect.center.y - img.height / 2
    

如果这些评论没有帮助,如果您 post 实际运行的代码片段(连同必要的图像文件)并显示问题,我可能会给出更好的答案。

@Kale Kundert 绝对让我找到了解决这个问题的正确方向。是的,组顺序是基本的,并且在本文档的帮助下:https://pyglet.readthedocs.io/en/latest/modules/graphics/ 我设法通过确保将我实现中的 Sprite 对象添加到前景组中来获得所需的行为:

class BasicButton(glooey.Button):
    def __init__(self, *, button_text, image_path):
        super().__init__(button_text)
        self.sprite = None
        self.image_path = image_path

    def do_draw(self):
        image_width, image_height = Image.open(self.image_path).size
        x = self.rect.left + self.rect.width/2 - image_width/2
        y = self.rect.bottom + self.rect.height/2 - image_height/2
        if self.sprite is None:
            self.sprite = pyglet.sprite.Sprite(
                img=pyglet.image.load(image_path), 
                x=x, y=y, 
                batch=self.batch, 
                group=self._foreground.get_group()
        )
        else:
            self.sprite.x = x
            self.sprite.y = y

注意 self._foreground.get_group() 的变化。

考虑到@Kale Kundert 的回答,也许这不是最正确的实现,但我的目标是在 hoovering/clicking 上获得具有响应背景的按钮的行为,这也允许我通过回调编辑显示在其上的文本。如果我添加为每种潜在情况创建按钮图标(图像),我将无法实现这种自由度。

这并不能完全回答最初的问题,但似乎 OP 真正想要做的是动态更改按钮的文本(例如通过回调)。这很容易做到,并且不需要重载任何父 class 方法。

要理解的重要概念是按钮实际上只是其他小部件的专用容器。具体来说,一个按钮包含一个前景小部件和一些背景小部件(每个鼠标状态一个)。要动态更改按钮,您只需访问相关的包含小部件(在本例中为前景)并进行更改。这种架构的好处在于,无论按钮中的小部件是什么类型,它都能正常工作。如果前景是Label,可以设置button.foreground.text。如果是Image,可以设置button.foreground.image,以此类推

这是一个简单但完整的示例,说明如何执行此操作。在此示例中,按钮滚动状态只是纯色而不是图像,按钮文本只是每次单击按钮时递增的数字,但希望它能展示如何做这样的事情。

import pyglet
import glooey

class MyButton(glooey.Button):
    custom_base_color =  40,  40,  40  # dark grey
    custom_over_color =  80,  80,  80  # normal grey
    custom_down_color = 240, 240, 240  # light grey

window = pyglet.window.Window()
gui = glooey.Gui(window)

# Note that arguments to the Button constructor are passed directly to the 
# constructor of the foreground widget, which is `Text` by default.
button = MyButton('1')
gui.add(button)

def on_click(button):
    i = int(button.foreground.text)
    button.foreground.text = str(i + 1)

button.push_handlers(on_click)

pyglet.app.run()

编辑:

我现在明白 OP 想要一个在前景中既有图像又有标签的按钮(当然还有 mouse-responsive 背景)。上面相同的 button-is-a-container 概念也适用于此。一个按钮只能有一个前景小部件,但前景小部件本身可以包含任意数量的小部件。例如,您可以将前景设置为 HBox,以便在一个按钮中放置多个小部件 side-by-side。

要制作一个前景在图像顶部有文本的按钮,诀窍是将前景小部件设为 Stack。然后您可以将文本和图像添加到该堆栈。

下面的例子展示了一种方法。请注意,前景堆栈被分解到它自己的自定义小部件 class 中,称为 MyImageLabel。我认为这使代码更加清晰和可重用,但您可以在不这样做的情况下获得相同的效果(即通过直接在 MyButton.Foreground 中创建堆栈,或者甚至在实例化按钮后创建堆栈并将其分配给button.foreground)。另请注意,文本和图像都可以通过回调进行修改,方法与上述几乎相同。

#!/usr/bin/env python3

import pyglet
import glooey

class MyImageLabel(glooey.Widget):
    Image = glooey.Image
    Label = glooey.Label

    def __init__(self):
        super().__init__()
        self._stack = glooey.Stack()
        self.label = self.Label()
        self.image = self.Image()

        self._stack.add(self.image)
        self._stack.add(self.label)

        self._attach_child(self._stack)

class MyButton(glooey.Button):

    class Foreground(MyImageLabel):

        class Image(MyImageLabel.Image):
            custom_image = pyglet.image.load('img0.png')

        class Label(MyImageLabel.Label):
            custom_text = '1'
            custom_alignment = 'center'

    custom_base_color =  40,  40,  40  # dark grey
    custom_over_color =  80,  80,  80  # normal grey
    custom_down_color = 240, 240, 240  # light grey

window = pyglet.window.Window()
gui = glooey.Gui(window)

button = MyButton()
gui.add(button)

def on_click(button):
    i = int(button.foreground.label.text)
    button.foreground.label.text = str(i + 1)
    button.foreground.image.image = pyglet.image.load(f'img{i%2}.png')

button.push_handlers(on_click)

pyglet.app.run()

如果你想运行上面的例子,你需要下载这些图片并分别命名为img0.pngimg1.png