Alignment without stretching

Hello to all,

Background Story: I very recently started investing in lemur as a replacement of Nifty gui. I think it has great potential because I was looking for a library that would allow me to extend things instead of introducing hard limitations because of bad decisions during its design.
And it seems that this is what’s all about with lemur. Actually I was about to create my own library until I found out that lemur already sets the building blocks for me and I don’t need to start from scratch. Its architecture makes a lot of sense to me and the code is well written.

Issue: All I want to do is the central alignment of a button without changing its size. In other words it needs to be in the center of a panel and fits its contents. The reason for this is that I don’t want the user to feel that he stepped on a minefield and every part of the screen is clickable.
Anyway what I managed to do so far is the one or the other but not both using SpringGridLayout.

I intent to implement my own GuiLayout that is more html like but before I do that I would like to know if there’s something I missing so I can have a better understading on the existing implementations before I start doing my own thing.

Here’s some code and screenshot of the problem:

@Override
    protected void initialize(Application app) {
        GuiGlobals.initialize(this.mainApp);
        mainApp.getGuiNode().attachChild(mainScreen());
    }

    private Container mainScreen() {
        Container screen = LemurUtils.getFullScreenContainer(mainApp.getCamera());
        screen.setLayout(new SpringGridLayout(Axis.Y, Axis.X, FillMode.Even, FillMode.Even));
        screen.setBackground(new QuadBackgroundComponent(ColorRGBA.Gray));

        screen.addChild(createButtonPanel("Start", this::onStartClick));
        screen.addChild(createButtonPanel("Select Level", this::onSelectLevelClick));
        screen.addChild(createButtonPanel("Credits", this::onCreditsClick));
        screen.addChild(createButtonPanel("Quit", this::onQuitClick));

        return screen;
    }

    private CircularListWrapper<ColorRGBA> color = new CircularListWrapper<>(Arrays.asList(ColorRGBA.Green, ColorRGBA.Blue));

    private Panel createButtonPanel(String label, Runnable action) {
        final Container container = new Container(new SpringGridLayout(Axis.Y, Axis.X, FillMode.None, FillMode.None));
//        final Container container = new Container(new BorderLayout());
        container.setBackground(new QuadBackgroundComponent(color.getNext()));

        {
            Button button = container.addChild(new Button(label));
//            Button button = container.addChild(new Button(label), BorderLayout.Position.Center);
            button.setBackground(new QuadBackgroundComponent(ColorRGBA.Red));

            BitmapFont customFont = mainApp.getAssetManager().loadFont(FilePaths.Fonts.ARIAL_BLACK_55);
            button.setFont(customFont);

            button.setTextHAlignment(HAlignment.Center);
            button.setTextVAlignment(VAlignment.Center);

            button.addClickCommands(source -> action.run());
        }
        return container;
    }

and this is what this code produces:

So the element are not stretched but they are aligned as top-left in relationship to their parents.

Btw maybe there is a different approach to follow here for the bigger probel which is having 4 buttons in the middle of the horizontal axes spread evenly in the vertical axes but with no re sizing.

1 Like

Thanks for providing the code example, I think some of it may be missing though? I don’t think LemurUtils and CircularListWrapper are core parts of the library/JME.

Yes indeed. I didn’t want to spam you with all the implementation details.

This is how I create the full screen container:

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class LemurUtils {

    public static Container getFullScreenContainer(Camera camera) {
        Container container = new Container();
        container.setLayout(new SpringGridLayout());
        final int width = camera.getWidth();
        final int height = camera.getHeight();

        container.setPreferredSize(new Vector3f(width, height, 0));
        container.setLocalTranslation(0, height, 0);
        return container;
    }
}

And the circularList is just used for debugging purposes:

@RequiredArgsConstructor
public class CircularListWrapper<T> {

    @Getter
    private final List<T> list;
    private int counter = 0;

    public T getNext() {
        final T nextResult = list.get(counter % list.size());
        counter++;
        return nextResult;
    }
}

Thanks, people are dramatically more likely to help if you provide a small program they can just run and see the issue. It isn’t always possible but when it is it is a good thing to do.

Here is a minimal program that replicates your issue

import com.jme3.app.SimpleApplication;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.system.AppSettings;
import com.simsilica.lemur.Axis;
import com.simsilica.lemur.Button;
import com.simsilica.lemur.Container;
import com.simsilica.lemur.FillMode;
import com.simsilica.lemur.GuiGlobals;
import com.simsilica.lemur.HAlignment;
import com.simsilica.lemur.Panel;
import com.simsilica.lemur.VAlignment;
import com.simsilica.lemur.component.QuadBackgroundComponent;
import com.simsilica.lemur.component.SpringGridLayout;

import java.util.Arrays;
import java.util.List;

public class LemurTest extends SimpleApplication{

    @Override
    public void simpleInitApp(){
        GuiGlobals.initialize(this);
        getGuiNode().attachChild(mainScreen());
    }

    private Container mainScreen() {
        Container screen = LemurUtils.getFullScreenContainer(getCamera());
        screen.setLayout(new SpringGridLayout(Axis.Y, Axis.X, FillMode.Even, FillMode.Even));
        screen.setBackground(new QuadBackgroundComponent(ColorRGBA.Gray));

        screen.addChild(createButtonPanel("Start", this::onClick));
        screen.addChild(createButtonPanel("Select Level", this::onClick));
        screen.addChild(createButtonPanel("Credits", this::onClick));
        screen.addChild(createButtonPanel("Quit", this::onClick));

        return screen;
    }

    private void onClick() {
        System.out.println("Clicked");
    }
    
    private final CircularListWrapper<ColorRGBA> color = new CircularListWrapper<>(Arrays.asList(ColorRGBA.Green, ColorRGBA.Blue));

    private Panel createButtonPanel(String label, Runnable action) {
        final Container container = new Container(new SpringGridLayout(Axis.Y, Axis.X, FillMode.None, FillMode.None));
        container.setBackground(new QuadBackgroundComponent(color.getNext()));

        {
            Button button = container.addChild(new Button(label));
            button.setBackground(new QuadBackgroundComponent(ColorRGBA.Red));
            button.setTextHAlignment(HAlignment.Center);
            button.setTextVAlignment(VAlignment.Center);

            button.addClickCommands(source -> action.run());
        }
        return container;
    }


    public static void main(String[] args){
        LemurTest app = new LemurTest();
        AppSettings settings = new AppSettings(true);
        settings.setResolution(800, 600);
        app.setSettings(settings);
        app.start();
    }

    public static final class LemurUtils {

        public static Container getFullScreenContainer(Camera camera) {
            Container container = new Container();
            container.setLayout(new SpringGridLayout());
            final int width = camera.getWidth();
            final int height = camera.getHeight();

            container.setPreferredSize(new Vector3f(width, height, 0));
            container.setLocalTranslation(0, height, 0);
            return container;
        }
    }

    public static class CircularListWrapper<T> {

        private final List<T> list;
        private int counter = 0;

        public CircularListWrapper(List<T> list){
            this.list = list;
        }

        public T getNext() {
            final T nextResult = list.get(counter % list.size());
            counter++;
            return nextResult;
        }
    }
}

I’ll have a play and see

Thank you very much!

Hmm, it does seem somewhat difficult. I think partially it is that “outside in” layouts like this are less usual than “inside out” layouts. By which I mean most of the time a parent grows to accommodate its children, rather than being a fixed size. (Nifty was very much enforced “outside in” layout).

I can see ways in which it could be done with a new layout that took a child and parameters on where to put unwanted space (so say you put 40% of unwanted space above for example then 60% of unwanted space would go below). I’m up for trying to write something like that but before I do @pspeed is the expert on lemur (and its author) so he may have a better suggestion

I think dynamic insets is what you might be looking for.

Here it is in the context of the “component stack”:

I admit to not reading the whole thread in detail… so forgive me if it has already been mentioned and didn’t work for some reason.

In code, something like:

button.setInsetsComponent(new DynamicInsetsComponent(0.5f, 0.5f, 0.5f, 0.5f));

…would perfectly center a button into whatever space the layout chose for it.

1 Like

Thanks, by the way. :slight_smile:

…that was precisely the reason for its design: reusable building blocks to either use the “built in” stuff or roll together in different ways. So Lemur tries to be very modular.

Ooo, very cool. I’ve been using Lemur for ages without knowing about insets (largely hacking around it by adding panels as spacers)

I think either this

    private Panel createButtonPanel(String label, Runnable action) {
        SpringGridLayout layout = new SpringGridLayout(Axis.Y, Axis.X, FillMode.Even, FillMode.Even);
        final Container container = new Container(layout);
        container.setBackground(new QuadBackgroundComponent(color.getNext()));
        
        Button button = new Button(label);
        button.setBackground(new QuadBackgroundComponent(ColorRGBA.Red));
        button.setTextHAlignment(HAlignment.Center);
        button.setTextVAlignment(VAlignment.Center);

        button.addClickCommands(source -> action.run());
        button.setInsetsComponent(new DynamicInsetsComponent(0.5f, 0.5f, 0.5f, 0.5f));
        container.addChild(button);
        
        return container;
    }

Or even simpler this

    private Panel createButtonPanel(String label, Runnable action) {
        Button button = new Button(label);
        button.setBackground(new QuadBackgroundComponent(ColorRGBA.Red));
        button.setTextHAlignment(HAlignment.Center);
        button.setTextVAlignment(VAlignment.Center);

        button.addClickCommands(source -> action.run());
        button.setInsetsComponent(new DynamicInsetsComponent(0.5f, 0.5f, 0.5f, 0.5f));
        return button;
    }

Would centre the vang.tsiligoneas’s example

Yes that’s it! It worked!

Insets seems to be a flexible way to position items. At least more flexible than a simple align enumeration.

I didn’t notice it at first because I thought it was an advanced way to stylize elements, I didn’t realize it’s something basic.

Thank you both @pspeed, @richtea

4 Likes