Search code examples
python-3.xopenglpygletglooey

Images on glooey buttons (pyglet) sometimes drawn sometimes not


I have a pyglet mainwindow on which I want to add some control buttons. An icon is drawn on each of these buttons.

Here is my minimalistic example:

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()

If I run this a couple of times I always get a different set of properly drawn buttons:

enter image description here

The question is, why is this happening and how could it be solved?


Solution

  • This doesn't exactly answer the original question, but it seems that what OP really wants to do is change the text of a button dynamically (e.g. via a callback). This is easy to do, and doesn't require overloading any parent class methods.

    The important concept to understand is that a button is really just a specialized container of other widgets. Specifically, a button contains a foreground widget and a handful of background widgets (one for each mouse state). To change the button dynamically, you just need to access the relevant contained widget (the foreground in this case) and change it. The nice thing about this architecture is that it works no matter what kind of widgets are in the button. If the foreground is a Label, you can set button.foreground.text. If it's an Image, you can set button.foreground.image, etc.

    Here's an simple but complete example of how to do this. In this example, the button rollover states are just solid colors instead of images, and the button text is just a number that increments each time the button is clicked, but hopefully it show how to do things like this.

    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()
    

    Edit:

    I now understand that OP wants a button that has both an image and a label in the foreground (and still has a mouse-responsive background, of course). The same button-is-a-container concept from above also pertains to this. A button can only have one foreground widget, but the foreground widget itself can contain any number of widgets. For example, you could make the foreground an HBox to put several widgets side-by-side within a button.

    To make a button where the foreground has text on top of an image, the trick is the make the foreground widget a Stack. You can then add the text and the image to that stack.

    The example below shows one way to do this. Note that the foreground stack is factored into its own custom widget class called MyImageLabel. I think this makes the code more clear and reusable, but you can get the same effect without doing this (i.e. by creating the stack directly in MyButton.Foreground, or even by creating the stack after the button is instantiated and assigning it to button.foreground). Also note that the text and the image can both be modified via callbacks, in pretty much the same way as above.

    #!/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()
    

    screenshot

    If you want to run the above example, you'll need to download these images and name them img0.png and img1.png, respectively:

    green border orange border