GUI planning: composition vs inheritance

Started by
21 comments, last by SyncViews 5 years ago

I'm making a GUI library to build a game with.

I started rather naively with a GUI class and then a window class and then a button class. I realized these share elements an I thought about using inheritance but then stopped myself. I decided entities and components are more flexible. Continuing this line of reasoning I realized that all GUI is just a tree of nested rectangles with various mouse interaction functions.

So I've decided to build my entire GUI out of a single class: Window class. This class will itself contain a vector of its children Windows and will have some data like location and size and a reference to its parent window. It will only have a location which is considered relative to its parent. The ultimate window will cover the entirety of the screen and be transparent. 

This post is more of a blog post than asking any questions. Leave a post if you think my idea is brilliant or stupid or if I'm looking at this wrong. thanks.

Advertisement

Looks like you are stumbling around in the dark.

You may want to read this tutorial: http://iki.fi/sol/imgui/

 

14 hours ago, maltman said:

So I've decided to build my entire GUI out of a single class: Window class. This class will itself contain a vector of its children Windows and will have some data like location and size and a reference to its parent window. It will only have a location which is considered relative to its parent. The ultimate window will cover the entirety of the screen and be transparent. 

Let's say you want a button, you probably want that button to react to hover. That hover code will be common to the button classes, but different to the hover code on a scroll bar. Where do you put that hover code? Do you have complex, generalised, hover code in your window class? Do you have to repeat yourself for each button? Repeat for drag, etc.

1 hour ago, Irusan, son of Arusan said:

Let's say you want a button, you probably want that button to react to hover. That hover code will be common to the button classes, but different to the hover code on a scroll bar. Where do you put that hover code? Do you have complex, generalised, hover code in your window class? Do you have to repeat yourself for each button? Repeat for drag, etc.

You make a button behavior class to contain all the logic for all the different states - hovered, disabled, pressed, tabbed, etc. A button class can then has-a ButtonBehavior and has-a Window. 

4 minutes ago, Hodgman said:

You make a button behavior class to contain all the logic for all the different states - hovered, disabled, pressed, tabbed, etc. A button class can then has-a ButtonBehavior and has-a Window. 

Well, yes, that's how I'd do it. I'm asking maltman how he intends to do it with his Windows all the way down approach.

On 3/3/2019 at 11:08 PM, Irusan, son of Arusan said:

Well, yes, that's how I'd do it. I'm asking maltman how he intends to do it with his Windows all the way down approach.

That's how I'd do it in a windows-all-the-way-down approach :D 

The main GUI layout engine just has a huge tree of windows to deal with, and has no idea what other behaviors actually "own" those windows. That's perfect decoupling!

I highly recommend NOT implementing an immediate-mode GUI (IMGUI) approach (wintertime mentioned an article about IMGUI).

They're great for throwaway code but literally nothing else.

On 3/3/2019 at 4:08 AM, Irusan, son of Arusan said:

Well, yes, that's how I'd do it. I'm asking maltman how he intends to do it with his Windows all the way down approach.

Well the way Hodgman described it is how I was thinking. Sort of treating all the behaviors of the window/entities like they were components to attach like legos.

I'm just trying to do this without using inheritance and with as little code as possible.

 

On 3/6/2019 at 6:27 PM, Nypyren said:

I highly recommend NOT implementing an immediate-mode GUI (IMGUI) approach (wintertime mentioned an article about IMGUI).

They're great for throwaway code but literally nothing else.

Can you elaborate on why IMGUI is poopoo and what are some alternatives you suggest?

On 3/2/2019 at 12:23 PM, maltman said:

I'm making a GUI library to build a game with.

 

3 hours ago, maltman said:

I'm just trying to do this without using inheritance and with as little code as possible.

Other then not using inheritance, the "making a GUI library" and "and with as little code as possible" will never happen ?  GUI's are complex beasts, and they tend to grow and grow.  This coming from personal experience and several attempts at doing both multiple times haha.

3 hours ago, maltman said:

Can you elaborate on why IMGUI is poopoo and what are some alternatives you suggest?

If you're making a generic, re-usable IMGUI it's going to be rather complex as you'll want to be able to reskin the controls, etc.  Is there a particular reason you want to write your own?  If this is for experience, great, carry on.  If you want to use it for release use an existing library.  If your goal is to make games, make games not GUI's.

I'm sure you know about Dear ImGui: https://github.com/ocornut/imgui it's a pretty awesome little utility if you just need something to tweak in game variables.

"Those who would give up essential liberty to purchase a little temporary safety deserve neither liberty nor safety." --Benjamin Franklin

5 hours ago, maltman said:

Can you elaborate on why IMGUI is poopoo and what are some alternatives you suggest?

The alternative is called "retained mode" - it's where you construct the GUI out of persistent instances (OOP objects, components, etc) in a data structure.  It doesn't matter whether it's inheritance or composition that you use, both are retained mode if you create a hierarchy of objects in a data structure and have the rendering and user interaction code refer to the hierarchical data structure instead of immediate mode code describing the structure.

Instead of calling "if (Button("Label", width, height)) { OnClick(); }" multiple times every time the user moves the mouse / resizes the window / presses a keyboard key, you write "button = new Button("Label", width, height);  Controls.Add(button);  button.Click += OnClick();", and you handle mouse movement, layout, etc with separate functions that traverse only the portions of the widget hierarchy that needs to be traversed at the moment.  For mouse movement events, you use the natural spatial partitioning that rectangular hierarchies provide.  This is a C# syntax example, but the language does not matter.

 

IMGUI Problem 1:  Associative book-keeping between the immediate-mode code and what the user sees.

IMGUI code typically involves extra work in re-associating the imperative-style code (such as  "if (Button("Label")) { OnClick(); }) back to the interactive elements that the user sees (which must have a persistent identity of some sort in order to be interacted with across multiple "frames").

For example, if you have a scrolling panel with several textboxes on it, and the keyboard focus is given to one of those textboxes, the user expects that textbox to keep its focus even if they scroll the panel such that the other textboxes are no longer visible.

This also means that if you suddenly need a GUI where you dynamically add or remove UI elements (such as culling them when they're not being rendered like in the scrolling panel example), you have to jump through extra hoops to make sure that your "if (Button(...))" call in one instance is associated with the same button that you end up rendering.

In some cases, the extra bookkeeping prevents your code from executing efficiently...

 

IMGUI Problem 2:  Performance issues due to wasteful execution of immediate-mode code.

The "if (Button(...))" pattern does multiple things:  It causes a button to be rendered at the given point in the layout, it checks to see if the user has clicked on the rectangular region that the button layout occupies, and it affects the layout of subsequent elements.

Since the IMGUI internal workings needs to adapt to the possibility that your immediate mode code doesn't actually call the Button function this time through, it needs to recalculate the layout every pass through the immediate mode code (or at least be able to detect whether anything has changed).  Depending on how many passes you need to make and how many widgets you have, this can add an immense amount of overhead.

For large scrolling panels, immediate mode code typically has no way to efficiently cull widgets which are off the screen due to the immediate mode code still needing to call each widget's function so that the correct association can be made between the widget that the user is interacting with and the immediate mode code which needs to handle user interaction for that widget.

 

IMGUI Problem 3:  Changing properties of UI elements requires re-executing ALL of the immediate mode code which can affect the layout of the single UI element you're changing.

In retained mode, you can just say "button.Color = Color.Red;" any time you have a reference to the button instance.  Internally, the .Color setter sets a flag telling the rendering code that it needs to refresh.  In Immediate mode, you are forced to make this change in the code which calls the Button(...) function, either before it using global state affecting the next called function (which is bad code design), or by passing an argument to the Button function (which requires that you set up the arguments to pass to Button before calling it).

If you attempt to make this approach more flexible by encapsulating the Button call and property setup code in an OOP class or struct-and-function pair, you have just converted to using a retained mode wrapper on top of immediate mode.  Just avoid this in the first place by using retained mode from the beginning.

 

Source:  I have extensive experience using Unity's IMGUI system for complex in-Editor tool windows, and have to constantly work around all of these headaches because Unity's Editor code still has no way to let me write retained mode UI.  Retained mode I've used include Unity's UGUI system (which unfortunately is only usable while the game is running, not in the Editor) and .Net's WinForms and WPF systems.  The retained mode systems are far better for creating complex UI than IMGUI is.  I haven't used any IMGUI systems other than Unity's, but I have never seen an article describing and implementation which works any differently or solves any of those problems.

This topic is closed to new replies.

Advertisement