kxgames / glooey

An object-oriented GUI library for pyglet.
MIT License
91 stars 6 forks source link

Attach dialogs to any root #47

Open UplinkPhobia opened 3 years ago

UplinkPhobia commented 3 years ago

Hey,

I tried creating a dialog that I'd add to a Board (to make a sort of "window" system). However closing them crashes as it tries to detach them from the gui and not the board.

Is there a clean way to do that? I tried replacing the open() and close() methods, but it doesn't really feel so clean.

    def open(self, root, *args, **kwargs):
        self.close()
        self.myparent = root
        root.add(self, *args, **kwargs)

    def close(self):
        if self.myparent is not None:
            self.myparent.remove(self)
            self.dispatch_event("on_close", self)
kalekundert commented 3 years ago

Dialogs are only meant to be attached to the GUI, because that's how they can ensure that they appear above all of the other widgets.

My first instinct is that if you want a dialog widget with a non-GUI parent, then probably the best approach is to make your own widget class (which is pretty much what you've done). But maybe it'd be helpful if you could explain in more detail what you're trying to accomplish.

UplinkPhobia commented 3 years ago

If the best way is to subclass, it's probably what I'll do if I need it.

Basically what I wanted to make (I'm just testing things, so it's not a big issue) was a sort of game interface, with menus on the sides and the map view in the middle (e.g. a 3-panes VBox, with the middle one being the one with the map and sprites and such and the others showing statuses, message logs, etc). My idea was that having popup menus that can be moved around and closed easily would be nice, for example an info popup or so, that you could move in a corner of the pane until you don't need it anymore. And I was trying to see if those popups could be "limited" to inside the map pane, so they wouldn't overlap with the other menus.

So as you can see, it's quite a specific use, that was more meant to be an experiment than a real need, and I'll manage if needed, I was just wondering if it was intentional to "limit" their root to the gui itself.

Thanks for the explanation by the way!

kalekundert commented 3 years ago

Ah, that makes sense. You'll definitely need custom widgets to get something like that, but it actually shouldn't be too hard. One thing to note is that you'll want to make use of ScrollPane to implement dragging*. Specifically, I think you'd want the middle pane of your VBox to be a Stack, with the bottom layer being the game map and the top layer being a ScrollPane (or possibly a custom ScrollPane subclass). You'd then add your dialog widgets to the ScrollPane, so that they'd appear on top of the map and could interact with the ScrollPane to be dragged around.

If you decide to do this, a lot of the code in glooey/scrolling.py might be informative. Specifically, Viewport is a good example of a ScrollPane subclass, and HVScrollBar.GripMover is a good example of how dragging might be implemented. Let me know if you have any questions. The scrolling module has a lot of tricky code and isn't very well documented, so it could be hard to understand. But I'm happy to answer questions, and getting questions would probably encourage me to improve the documentation...

TBH, there probably should be a built-in draggable dialog class that basically implements the scheme I outlined above. Not sure I'll get to that, but I would be interested in a PR if you end up implementing something similar. It might also make sense to generalize the Dialog class so that it's parent can be any Stack, but I'll have to think about that some more.

*Manually updating the coordinates of the widget (e.g. using a Board or something) is very inefficient, because it forces glooey to recalculate the positions and sizes of all the widgets composing the dialog each frame. ScrollPane avoids this by using OpenGL to change where the dialog is rendered, without changing its underlying coordinates.

UplinkPhobia commented 3 years ago

I managed to make a sort of working prototype with a board inside a scrollpane (I just used it to avoid crashes when the dialogs were dragged out of the window), since it felt the board was the only easy way to move widgets like that. It does indeed seem to be performance heavy, as it started lagging heavily when adding too many dialogs.

I'll try the solution you gave, but aren't ScrollPane only used to move the whole "view"? In such a case I'd need one pane per dialog if I want to show multiple dialogs?

In any case, I'll experiment and dig through the code, and if I manage to make something clean I could make a PR. Need to find motivation to work on it though :')

kalekundert commented 3 years ago

That's a good point, you would need one pane per dialog. That might not be a bad thing though, because it'd force you to be explicit about the layering of the dialogs (since each dialog would be associated with a different pane in a different layer of the stack). Maybe a good API would hide the scroll pane from the user, e.g.:

class DraggableDialog:

    def __init__(self):
        self.pane = ScrollPane()
        self.pane.add(self)

    def open(self, stack):
        stack.add(self.pane)

    def close(self):
        self.pane.parent.remove(self.pane)
kalekundert commented 3 years ago

A quick correction: every time I mentioned ScrollPane previously, I should've said Mover. Mover implements translation via OpenGL, while ScrollPane implements clipping. It's the former that's relevant to dialogs.