Tonegodgui with layout managers

Okay so I’ve been working with Tonegodgui for a few months, and have just completed porting my old Nifty based UI to Tonegod. It was quite satisfying clicking on the button to remove the Nifty library (nothing against Nifty of course, I am just happy the porting is done with no loss of functionality). I am now past the point of no return :slight_smile:

On the whole, I am very pleased with the result. My UI looks nicer, my code is WAY saner and the only XML in sight is the style map (this makes me very happy). I love the fact it is native to JME and properly part of the scene, with all the goodies that comes with.

In the course of porting I have built up a little collection of custom controls, observations, and one fairly major customisation that may just be of use to others. Basically, a Swing style “Layout Manager” system.

I see reading through the forums, that comments have been made about one of the disadvantages is the absolute positioning. I know about the Docking/Resize attributes, but these are not really flexible enough for my needs, and I think such functionality is better delegated to something separate from the components themselves.

After spending so many years using Swing and other toolkits that use layout managers, this approach is very natural to me and so I decided to try and apply the same to Tonegodgui. Now I’ve completed the porting of my game UI, I have a fairly reasonable working layout management system bolted on top of Tonegodgui :slight_smile: Some basic points about how I implemented this …

  • Each existing control has a counterpart “LayoutAwareSomeControlName”. Most of these extend the core control and add layout features. You can sort of mix controls, but in most cases it’s best to either use LayoutAware controls in LayoutAware contains, or not at all. Note, I have converted MOST, not all components (i.e. the ones I actually use ;).

  • No changes to Tonegodgui core. Well, unfortunately there was 1 change I couldn’t avoid, more on this later. To achieve this there were a load of places where I had to resort to nasty hacks and reflection to get at some internals of Tonegodgui. All of this could have been avoided if I had modified the base source, but I wanted to treat my layout management code as something separate, as I didn’t know how much the core library would change.

  • All container like controls (e.g. LayoutAwarePanel) have a layout manager that may be set on them. addChild() is overidden so that each time a child is added the container laid out again (and also a “addChild(Element, Object)” where the 2nd argument is for layout manager specific constraints). This is a bit wasteful, and I will eventually find a better solution. Layout may of course also be invoked manually. When an element is laid out, all of it’s children are laid out to (by invoking their own layout managers when used).

  • Most compound controls (e.g. LayoutAwareCheckBox) also have a specific default layout manager (although it may be changed). In fact Checkbox is an odd one. It’s bounds are the bounds of the Check Icon, not the entire component. So setting the bounds on CheckBox doesn’t act as you’d expect it. It was for this reason I added component specific layout managers.

  • All components have the concept of min/max/preferred sizes. For the most part, preferred size is calculated by default. For example, Labels give a preferred size calculated from their text (a buttons width does the same). Containers take on the total preferred size of their child components (using their set layout manager). There are setters and getters added for each of these 3 sizes that may be used to override per element. Layout managers uses these sizes as hints and usually respect all 3 values, although ultimately it will calculate the components geometry as it sees fit.

  • Windows can be pack()'ed, i.e. all the components added and the window size determined automatically. One caveat here is text elements, who by default want to span a single line and so larger text will have a large preferred width. Combat this by setting a preferred size and only the height will be adjusted so all text fits.

  • I have written a number of layout managers, mostly similar to Swing. This includes the completely awesome MigLayout (http://www.miglayout.com/) which conveniently is toolkit agnostic, so was pretty easy to add it as all the hard actual layout work is done by the library. This is the layout manager I use the most as it covers an awful lot of my layout needs. Other provided managers include FlowLayout (layout left to right in a row, or top to bottom), BorderLayout (N,S,E,W or center), FillLayout (all children overlaid at full container dimensions), GridLayout (fixed size grid, all children equal sizes), Basic (standard absolute positions and sizes).

  • All LayoutAware constructors (except Window type ones) have their position arguments removed, and for the most part you shouldn’t use dimensions either, although the argument is still there.

All of this has meant I pretty much don’t worry about positioning AT ALL (well, except for root windows), and mostly sizing is taken care of. For example, the follow is all that’s need to have a form where the first two rows consist of 2 cells, the first being a label that grows to take up extra space, and a single button on the final row that spans the whole width of the contain (think a typical login screen).

[java]LayoutAwarePanel window = new LayoutAwarePanel(screen, new Vector2f(100, 100));

// Set layout. 2 cols, 3 rows. insets of 10 pixels around whole panel, gap of 4 between elements.
window.setLayoutManager(new MigLayout(screen, “gap 4, ins 10, wrap 2”, “[fill, grow][]”, “[][][]”);

// Row 1
LayoutAwareLabel l1 = new LayoutAwareLabel(screen);
l1.setText(“Label 1:”);
window.addChild(l1);
LayoutAwareLabel v1 = new LayoutAwareLabel(screen);
v1.setText(“Value 1”);
window.addChild(v1);

// Row 2
LayoutAwareLabel l2 = new LayoutAwareLabel(screen);
l1.setText(“Label 2:”);
window.addChild(l2);
LayoutAwareLabel v2 = new LayoutAwareLabel(screen);
v1.setText(“Value 2”);
window.addChild(v2);

// Row 3
LayoutAwareButton b1 = new LayoutAwareButton(screen);
l1.setText(“Button 1”);
window.addChild(l2, “span 2”);

//
window.pack();
screen.addElement(window);
[/java]

Soo … my question is, is there any interest in this code? If so, I can extract it out from my project and make it available somehow. This should be a pretty quick task. Turning it into a patch against the base Tonegodgui source would take a bit more time, but is where I am likely to go with this (for my own purposes).

As noted above, there are a couple of tweaks needs to the base Tonegodgui code to make this work properly. The main one is that in Element.java, the “elementChildren” member uses a HashMap. The problem with this, is if you hide elements, and make them visible again, the order in which they were added is effectively lost. Layout Managers rely on the order of the components when setting bounds, so to combat this I had to change elementChildren to be a LinkedHashMap instead. I don’t think that has any other adverse effects.

RR

3 Likes

That’s interesting.
Can you extend Element.java and add some kind of index? Just not to change the core?
I think i’ll move my World Editor to ToneGodGui…

@mifth said: That's interesting. Can you extend Element.java and add some kind of index? Just not to change the core?

Well all the extended components extend their non-layout aware version, not Element. So each component would need to do the same thing. Many layout aware components already override addChild() so the tracking of the index could be done there (removing would need to do the same I guess).

I also considered using reflection to alter the actual map class being used as each component is constructed. Seems a bit crazy, but there are other crazy things being done as well … so maybe that doesn’t matter.

At the time … a one line change to Element was WAY easier :wink:

I think i'll move my World Editor to ToneGodGui...

This is largely why I built the layout managers. While I could have put up with it for the game UI itself, my world editor has lots of dynamically built forms, showing quite a lot of detail and having elements that appear or disappear depending on state (changing the layout). Managing this using X/Y co-ordinates for everything would have been annoying at best.

I’ve finally gotten around to extracting this from my project into a separate library. The relevant links …

1 Like

I’m really liking this and would like to include it in the library as a standard layout manager (if you are okay with that…). Just let me know!

@t0neg0d said: I'm really liking this and would like to include it in the library as a standard layout manager (if you are okay with that...). Just let me know!

Of course! I would love to see this :slight_smile: I spent last night tidying things up a little and renaming some classes, e.g. instead of LayoutAwareXXXX I changed so all my extended controls are just prefixed by “L”. Tonight I will add documentation too. I also need to complete this work for ALL controls. I should be able to get to them this coming weekend.

Depending on how exactly you wish to include it, there are a few core changes that would greatly reduce the amout of code that the extended layout controls are comprised of. The following changes are mostly about getting rid of the nasty reflection hacks used to get at some hidden member variables.

Sorry for the following wall of text!

Patches

First there is a reasonably important patch. I mentioned it before, it just makes elementChildren use a LinkedHashMap so the order the child components are added is kept.

element-children-linked-map.patch

There are some other patches there that may be useful (nothing to do with layout). Firstly my “Modelled Slider” and “Modelled Spinner” classes. Also a change to SlideTray behavior. If a tab header is partially visible, the first click on the arrow will slide it so it is fully revealed. The second will slide to the next (if there is one). This just feels smoother to me, you dont get half visible tab headers. The final extra patch adds a hook for right-clicking on a tab header.

Variables

I’ll list each class and the variables and whether getter or setter is needed. They are currently access through reflection.

Element

  • orgPosition (read)
  • elementChildren (read)
  • minDimensions (read)

DragElement

  • originalPosition (read). This may no longer be required, it’s used for a fix to spring back.

Screen

  • mouseXY (read)
  • mouseFocusElement (read)
  • tabFocusElement (read)

ScrollArea

  • scrollSize (read)
  • scrollHidden (read)

VScrollBar

  • btnScrollTrack (read)
  • btnScrollUp (read)

TabControl

  • tabs (read)
  • tabButtonGroup (read)
  • tabPanels (read)
  • tabXInc (read)
  • tabButtonIndex (read/write)
  • tabSlider (read)
  • tabWidth (write)
  • tabHeight (write)
  • tabResizeBorders (write)

RadioButtonGroup

  • radioButtons (read)

Slidetray

  • trayElements (read)
  • trayPadding (read)
  • currentElementIndex (read)

Methods

There are a few methods thats it would be handy to be able to call as well (generally to fix layout after things have changed).

Element

  • updateTextElement

TextField

  • centerTextVertically

Changes To Tonegodgui

There are some maybe slightly more intrusive changes, again that would help in reducing the size of these extensions. In some cases this might end up meaning the extended components are not needed at all.

Sizes

All layout aware components have something like this (actually defined in the interface LayoutConstrained). If this were to move up into Element, that would cut out a lot of duplicated code, and the LayoutConstrained interface would not be needed either.

[java]
private Vector2f prefDimensions;
private Vector2f maxDimensions;

@Override
public Vector2f getMinDimensions() {
// Tonegodgui already has a min dimension, but needs a hack to get at it (minDimensions)
    return ControlUtil.getElementMinDimension(this);
}

public Vector2f getMaxDimensions() {
    return maxDimensions;
}

public Vector2f getPreferredDimensions() {
    return prefDimensions == null ? calculatePreferredDimensions() : prefDimensions;
}

protected Vector2f calculatePreferredDimensions() {
// Some control specific algorithm for calculating best size. Often this will
// either be 'orgDimensions', or in the case of container-like controls, the
// layout manager will be queried to get the best size for the children. Some
// controls will have other methods, such as Label using font metrics. By
// default, return the orgDimensions
return getOrgDimensions();
}

public void setMaxDimensions(Vector2f maxDimensions) {
    if (this.maxDimensions == null) {
        this.maxDimensions = new Vector2f();
    }
    this.maxDimensions.set(maxDimensions);
}

public void setPreferredDimensions(Vector2f prefDimensions) {
    if (this.prefDimensions == null) {
        this.prefDimensions = new Vector2f();
    }
    this.prefDimensions.set(prefDimensions);
}

[/java]

Layout Manager

All the extended container-like controls, and a number of controls themselves may have a LayoutManager set on them. If this could be moved to Element, again, a chunk of code could be removed.

[java]
private LayoutManager layoutManager;

public LayoutManager getLayoutManager() {
    return layoutManager;
}

public void setLayoutManager(LayoutManager layoutManager) {
    this.layoutManager = layoutManager;
}

@Override
public void addChild(Element child) {
    addChild(child, "");
}

public void addChild(Element child, Object constraints) {
    super.addChild(child);
    if (layoutManager != null) {
        layoutManager.constrain(child, constraints);
        layoutManager.layout(this);
    }
}

[/java]

@rockfire said: Of course! I would love to see this :) I spent last night tidying things up a little and renaming some classes, e.g. instead of LayoutAwareXXXX I changed so all my extended controls are just prefixed by "L". Tonight I will add documentation too. I also need to complete this work for ALL controls. I should be able to get to them this coming weekend.

Depending on how exactly you wish to include it, there are a few core changes that would greatly reduce the amout of code that the extended layout controls are comprised of. The following changes are mostly about getting rid of the nasty reflection hacks used to get at some hidden member variables.

Sorry for the following wall of text!

Patches

First there is a reasonably important patch. I mentioned it before, it just makes elementChildren use a LinkedHashMap so the order the child components are added is kept.

element-children-linked-map.patch

There are some other patches there that may be useful (nothing to do with layout). Firstly my “Modelled Slider” and “Modelled Spinner” classes. Also a change to SlideTray behavior. If a tab header is partially visible, the first click on the arrow will slide it so it is fully revealed. The second will slide to the next (if there is one). This just feels smoother to me, you dont get half visible tab headers. The final extra patch adds a hook for right-clicking on a tab header.

Variables

I’ll list each class and the variables and whether getter or setter is needed. They are currently access through reflection.

Element

  • orgPosition (read)
  • elementChildren (read)
  • minDimensions (read)

DragElement

  • originalPosition (read). This may no longer be required, it’s used for a fix to spring back.

Screen

  • mouseXY (read)
  • mouseFocusElement (read)
  • tabFocusElement (read)

ScrollArea

  • scrollSize (read)
  • scrollHidden (read)

VScrollBar

  • btnScrollTrack (read)
  • btnScrollUp (read)

TabControl

  • tabs (read)
  • tabButtonGroup (read)
  • tabPanels (read)
  • tabXInc (read)
  • tabButtonIndex (read/write)
  • tabSlider (read)
  • tabWidth (write)
  • tabHeight (write)
  • tabResizeBorders (write)

RadioButtonGroup

  • radioButtons (read)

Slidetray

  • trayElements (read)
  • trayPadding (read)
  • currentElementIndex (read)

Methods

There are a few methods thats it would be handy to be able to call as well (generally to fix layout after things have changed).

Element

  • updateTextElement

TextField

  • centerTextVertically

Changes To Tonegodgui

There are some maybe slightly more intrusive changes, again that would help in reducing the size of these extensions. In some cases this might end up meaning the extended components are not needed at all.

Sizes

All layout aware components have something like this (actually defined in the interface LayoutConstrained). If this were to move up into Element, that would cut out a lot of duplicated code, and the LayoutConstrained interface would not be needed either.

[java]
private Vector2f prefDimensions;
private Vector2f maxDimensions;

@Override
public Vector2f getMinDimensions() {
// Tonegodgui already has a min dimension, but needs a hack to get at it (minDimensions)
    return ControlUtil.getElementMinDimension(this);
}

public Vector2f getMaxDimensions() {
    return maxDimensions;
}

public Vector2f getPreferredDimensions() {
    return prefDimensions == null ? calculatePreferredDimensions() : prefDimensions;
}

protected Vector2f calculatePreferredDimensions() {
// Some control specific algorithm for calculating best size. Often this will
// either be 'orgDimensions', or in the case of container-like controls, the
// layout manager will be queried to get the best size for the children. Some
// controls will have other methods, such as Label using font metrics. By
// default, return the orgDimensions
return getOrgDimensions();
}

public void setMaxDimensions(Vector2f maxDimensions) {
    if (this.maxDimensions == null) {
        this.maxDimensions = new Vector2f();
    }
    this.maxDimensions.set(maxDimensions);
}

public void setPreferredDimensions(Vector2f prefDimensions) {
    if (this.prefDimensions == null) {
        this.prefDimensions = new Vector2f();
    }
    this.prefDimensions.set(prefDimensions);
}

[/java]

Layout Manager

All the extended container-like controls, and a number of controls themselves may have a LayoutManager set on them. If this could be moved to Element, again, a chunk of code could be removed.

[java]
private LayoutManager layoutManager;

public LayoutManager getLayoutManager() {
    return layoutManager;
}

public void setLayoutManager(LayoutManager layoutManager) {
    this.layoutManager = layoutManager;
}

@Override
public void addChild(Element child) {
    addChild(child, "");
}

public void addChild(Element child, Object constraints) {
    super.addChild(child);
    if (layoutManager != null) {
        layoutManager.constrain(child, constraints);
        layoutManager.layout(this);
    }
}

[/java]

The modeled lists… This post got corrupted for me and I can’t open it. I couldn’t tell who originally posted, due to the changes in the forum. Thanks for posting this!! Now I know who to talk to about it.

Now, I’ll need some time to go through the rest of this =)