Fixing EasyGUI toolbox

Recently I came across a Matlab toolbox EasyGUI. Following its name, It supposes to ease the development of GUI apps in Matlab. A typical use case is very familiar: you need a convenient way for a user to enter a numeric value and for your program to get it as a variable. You know how it goes: you make a label, then an edit box, then you write a callback to get the value or to act upon value change. You can go further and add a slider, connect slider with your edit box. You end up with a lot of boilerplate code. Then you might need another user-entered value, so you will need to repeat all the steps from above. And on it goes.

EasyGUI handles all the boilerplate for you. You want a value, you get the value.

EasyGUI is a classic abandonware. It has been written in pre R2014b era (it's first version dates back to 2009). R2018a is the current Matlab release as of the time of writing. EasyGUI has no maintenance support, but seems like people still try to use it since it had been MATLAB Central pick of the week. If you read EasyGUI's latest comments on File Exchange, you will see people say EasyGUI does not work. When I first tried it, I had exactly the same problem as others: I followed the example, fixed some string case errors and ended up with a figure without any controls. Or at least without any visible controls. I could just move on to another toolbox, but at the same time I was tempted to figure out what was wrong with it. And I wanted to use EasyGUI in a small program I had to write anyway.

EasyGUI uses an undocumented control uiflowcontainer under the hood. When you create, say a slider, several controls are created. Important stuff: uiflowcontainer is used as a container, the rest of widgets are created as it's children. Uiflowcontainer is by itself a child of some other container. EasyGUI uses classes, events, interfaces, custom position class and it might be hard to figure out what happens where. On top of it comes good encapsulation. Internal variables are marked as private and you can't inspect them from outside (unless you modify the code, of course).

The confusing no widgets situation happens when people follow an example and use autogui widget. Autogui creates a layout and maintains position of all of its children. Here is the layout hierarchy from the source code of autogui:

    UiHandle
      UiMainContainer [uiflowcontainer] - contains guiarea & plotarea
         UiPlotArea [uipanel]
         UiGuiArea [uipanel]
             UiGuiPanelGroup [uiflowcontainer]
                UiCurrentGuiPanel [uiflowcontainer]

UiGuiPanelGroup can have multiple children whereas the currently active panel is referenced through UiCurrentGuiPanel. When all these panels are created, there are no widgets assigned to them. EasyGUI resizes UiGuiArea/UiGuiPanelGroup to be 1x1 pixel. When a new widget is added to UiCurrentGuiPanel, it is expanded to accommodate the new widget. Let's have a look at it's size at different stages. Here is the code in R2017b:

>> mg = gui.autogui;
>> fprintf('UiGuiPanelGroup before any widgets\n\tPosition: [%u %u %u %u], HeightLimits: [%u %u]\n', get(mg.UiGuiPanelGroup, 'Position'), get(mg.UiGuiPanelGroup, 'HeightLimits'));
UiGuiPanelGroup before any widgets
    Position: [1 1 556 416]
>> mass = gui.slider('mass', [10 20], mg);
>> fprintf('UiGuiPanelGroup after adding one widget\n\tPosition: [%u %u %u %u]\n', get(mg.UiGuiPanelGroup, 'Position'));
UiGuiPanelGroup after adding one widget
    Position: [1 1 556 416]

And the same code in 2014a:

>> mg = gui.autogui;
>> fprintf('UiGuiPanelGroup before any widgets\n\tPosition: [%u %u %u %u]\n', get(mg.UiGuiPanelGroup, 'Position'));
 UiGuiPanelGroup after adding one widget
    Position: [1 1 201 0]
>> mass = gui.slider('mass', [10 20], mg);
>> fprintf('UiGuiPanelGroup after adding one widget\n\tPosition: [%u %u %u %u]\n', get(mg.UiGuiPanelGroup, 'Position'));
UiGuiPanelGroup after adding one widget
    Position: [1 1 201 56]

Now, you see that in R2017b height is big right from the beginning. Height is adjusted dynamically in R2014a. I think it works correctly in R2014b because of figure visibility set to off (and some internal Matlab processing of invisible figures). If one adds a single fprintf, that accesses UiGuiPanelGroup position inside the constructor of gui.autogui, then the whole thing stops working! Here it is:

>> mg = gui.autogui;
Position of UiGuiPanelGroup right after creation: [1 1 556 416]

So the problem is that panel is too tall, widgets are placed outside the main figure. Hence they are invisible to user.

Without going any much deeper into it, there are several ways to change the above behaviour:

  1. Make UiGuiPanelGroup height appropriate right from the beginning and change it only if the main container's height is changed.
  2. Dynamically change UiGuiPanelGroup whenever UiGuiArea is changed.
  3. Do not do any size management, introduce a dependency from a different layout library. For example, one can use GUI Layout Toolbox. However, some size management can still be needed.
  4. Use normalized units instead of pixels. This will ensure that children are updated when parent is resized.

As I wanted a quick fix I decided to go with 2. The code available on GitHub and File Exchange. Note that I didn't improve the overall structure of the toolbox by modern standards. There is no packaging, tests, and good documentation.

Bonus.
Label widget allows to control top and bottom margin. However, top/bottom margins seems to be swapped. This is fixed as well.

Bonus 2.
Widgets appear to be clipped at the bottom. This is the consequence of internal size management. I fixed it as well.

I think that the library still needs a good code refactoring. Just have a look at this nice bug in the original version happening when user changes widget's label location multiple times in a row.
EasyGui-label-location-bug-1

If anyone interested in contributing, you know where the source code is. The license is EasyGUI's original license, which resembles MIT to me. However, as people do say in such cases - this is not a legal advise, consult your lawyer if needed.