Forcing lemur elements in size and position?

Is there a way, either regular or a dirty hack, to force Lemur elements to maintain a specific size and stay at a specific position?

The current way of placing stuff allows for too little control.

Can you be more specific?

Normally UIs use a layout that helps organize the components. There is quite a lot of flexibility in how this works. You are of course free to not use a container and just place things manually wherever you want… then deal with the cascade of issues whenever you want to make something slightly wider, a little to the left, wider border, etc…

It’s almost always better to figure out how to get the container + layout to work when possible.

You are largely right, containerisation is almost very helpful. But what is missing is the ability to set either max and min sizes.

That whole idea requires an iterative layout… which in itself requires almost 3x the code to handle than what is there now, along with complicated tree locking, and so on. (Take a look inside Java Swing sometime, easily half the code is dedicated to sorting out the tricky issues with iterative layouts.) With min/max sizing, it’s impossible to calculate sizing in one pass.

It also turns out to be rarely required with a little forethought and organization. Swing had to support dynamically resizable windows, fortunately Lemur does not… so often times just a few small tweaks can get a layout to work as desired.

I think in all of my Lemur UIs (for which there are many), I’ve only had one case where I wanted to know what size a window would be if a contained child was a certain size and I ultimately just ended up doing that a different (in hindsight: more correct) way.

So if we can talk about specific examples then I might be able to offer advice and/or give you ways you could abuse the current layout support to get something like min/max when iteration would not be necessary.

Or if it’s unclear why min/max implies iterative layout, I can explain that, too.

1 Like

That is a good thing. When playing a game, I am hardly interested in resizing windows at all.

I think I should start with some more insight of what is happening. But somehow I even struggle to get a border around each container for debugging purposes. This code in the stylesheet does not seem to work:

def border = TbtQuadBackgroundComponent.create(
                                        texture( name:"/com/simsilica/lemur/icons/border.png", 
                                                 generateMips:false ),
                                                 1, 1, 1, 6, 6,
                                                 1f, false );
// Only for debugging. Put red border around everything.
selector( "ungamunga"){
    border = border.clone();

This is my specific example:
(I managed to color a lot of lables and containers with a random color. The coder who created the randomColor-method for ColorRGBA needs to get a Nobel Prize)

The most urgent problem is that it is to wide, I want it to be smaller. And I would like to loose the space above every text.

    void makeEvolutionMenu(){
        Label  l;
        Container window = createWindow(0,"Training","evolution.png");
        Container row;
        row.addChild(new Label("Round: "),0,0);
        qaRounds = row.addChild(new Label("0"),1,0);
        row.addChild(new Label("Time: "),2,0);
        qaTime = row.addChild(new Label("0"),3,0);

        // ---------------------------------------------------------------------
        row.addChild(new Label("Surv: "),0,1);
        qaSurvivors= row.addChild(new Label("0"),1,1);
        qaTotalGuys= row.addChild(new Label("/ 200"),2,1);
         row.addChild(new Label("Survivors",idSmallLabel),0,0);
        row.addChild(new Label("Actions",idSmallLabel),1,0);
        row.addChild(new Label("Fitness",idSmallLabel),2,0);       
        l= row.addChild(new Label("","ungaGraphHolder"),0,1); 
        survivalGraph=new BarGraph(app, l, 1,app.stats.totalsPerRound, StatsData.COUNTERS.Survivors);
        l= row.addChild(new Label("","ungaGraphHolder"),1,1); 
        actionGraph=new BarGraph(app, l, 1,app.stats.totalsPerRound, StatsData.COUNTERS.Action_Executed);
        l= row.addChild(new Label("","ungaGraphHolder"),2,1);
        fitnessGraph=new BarGraph(app, l, 1,app.stats.totalsPerRound, app.fittnesCriteria);

        row=createRow("Fittness: ",window);
        VersionedList<String> testList = enumToVList(StatsData.COUNTERS.class);
        Selector lb=new Selector(testList, "ungamunga");
        controls.put("EVO_Fittness", (Panel)lb);

qaRounds.setBorder(new QuadBackgroundComponent(ColorRGBA.randomColor()));
qaTime.setBorder(new QuadBackgroundComponent(ColorRGBA.randomColor()));     
qaSurvivors.setBorder(new QuadBackgroundComponent(ColorRGBA.randomColor())); 
qaTotalGuys.setBorder(new QuadBackgroundComponent(ColorRGBA.randomColor())); 
    Container createWindow(int _idx,String _title, String _icon){
        Container window = new Container(new SpringGridLayout(Axis.X, Axis.Y,FillMode.Last,FillMode.Last),idWindow);
        Label title=window.addChild(new Label(_title,idTitle),0);
        title.setIcon(new IconComponent("Interface/Icons/"+_icon,new Vector2f(.8f,.8f),2,2,1,false ));
        Container content=new Container(new SpringGridLayout(Axis.X, Axis.Y,FillMode.Last,FillMode.Last));
        if (GUIDBG){
            title.setBorder(new QuadBackgroundComponent(ColorRGBA.randomColor()));
            content.setBorder(new QuadBackgroundComponent(ColorRGBA.randomColor()));
        return content;
    Container createRow(String _title,Container _window){

        if (_title!=""){
            Label l=new Label(_title,idRowTitle);
            _window.addChild(l, 0, rowCount++ );
            if (GUIDBG) l.setBorder(new QuadBackgroundComponent(ColorRGBA.randomColor()));
        Container row = new Container(new SpringGridLayout(Axis.X, Axis.Y,FillMode.ForcedEven,FillMode.Last),idRow);
        _window.addChild(row, 0, rowCount++ );
        if (GUIDBG){
            row.setBorder(new QuadBackgroundComponent(ColorRGBA.randomColor()));

        return row;

This the most relevant part of the stylesheet:

selector( "ungamunga" ) {
    fontSize = 20
    font = font("Interface/Fonts/DelinquentRegular.fnt")
    color = color(0,0,0, 1.0) 


selector( "_window", "ungamunga" ) {
    insets = new Insets3f( 5, 5, 5, 10 );
    background = windowBG.clone()
    background.setColor(color(1.0, 0.878 ,0.816 , 1.0))  
    preferredSize = new com.jme3.math.Vector3f(280, 320, 1);

selector( "_title", "ungamunga" ) {
    insets = new Insets3f( 10, 10, 20, 10 ); 
    font = titleFont
    fontSize = 24
selector( "_rowTitle", "ungamunga" ) {
    insets = new Insets3f( 5, 10, 0, 0 ); 
    color = color(0.102,0.078,0.078, 1.0) 
    font = titleFont
    fontSize = 18
selector( "_row", "ungamunga" ) {
    insets = new Insets3f( 5, 10, 0, 0 ); 
selector( "_smallLabel", "ungamunga" ) {
    insets = new Insets3f( 0, 0, 0, 0 ); 
    fontSize = 16


selector( "label", "ungamunga" ) {
    textHAlignment = HAlignment.Left
    textVAlignment = VAlignment.Top
    border = border.clone();


selector( "container", "ungamunga" ) {
    textHAlignment = HAlignment.Left
    textVAlignment = VAlignment.Top


selector( "label", "ungaGraphHolder" ) {
    preferredSize = new com.jme3.math.Vector3f(90, 40, 1);
    background = graphBG.clone()

But it will only be as wide as the things in it. Some child is forcing it to be that wide because it’s preferred size says it should be that wide. Maybe the graphs?

This may require some experimenting to figure out where the space is coming from. It could even be coming from the font itself which will be hard for Lemur to overcome. But from a brief glance through your style code and make some guesses, it seems like all of your ‘text with space above’ might be picking up an insets with a top value > 0. “Action Executed” seems ok. All other text seems like it has insets top=someValue, bottom=9

If it were me and randomly poking around, I’d set insets to different values to see what happens.

That code will only put a red border around anything without a border already, I think. And if it’s just for testing, a regular colored QuadBackgroundComponent with a margin might be a better test.

I got rid of the FillMode.Last and Evens, that helped with the width of the entire interface. I also got rid of all insets and added just a few that were really necessary.

It is getting better, but still some little things that I need to change. Have a look at this picture:

The spinner controls take the size of their contents. No matter my preferredSize settings, they keep changing size.

I also can not get the numbers in the spinner to vertical align in the center.

And there is a margin - or padding in css terms - inside the selector and spinner controls that I don’t know how to change.

1 Like

I don’t have time at the moment to look into it too deeply but my recollection is that a lot of times the spinners and selectors are slaves to the font because they use font characters for the “V” and the +/-. I can’t remember whether switching to an icon let me tighten these up for my own UIs or if using negative insets was how I did it. (Edit: to clarify, fonts in general have a ton of space above/below their characters… especially in the case of +/-)

…either way, it’s styling on the selector/spinner buttons. You might try just making those butons’ font size really tiny to see if that’s it before going to more extreme measures.

Icons for the buttons of the controls would be my next question. How do I do that in the style sheet? Can’t find any example with icons.

…and I am have a look at the font to see if there is any useless spacing.

icon = YourComponentHere

It’s not necessarily “useless” spacing but fonts by their nature will have spacing. A font line has to be able to accommodate the tallest and lowest hanging characters. And for a “-” or a “+” that will look like a ton of space.

Found it out…

def thumbIcon =new IconComponent( "/Interface/Icons/thumb.png", 1.0f,
                                 0, 0, 1f, false ); 
selector("spinner.down.button", "ungamunga"){
   insets = new Insets3f( 0, 0, 0, 0 );
   PreferredSize = new com.jme3.math.Vector3f(6, 6, 1)
   fontSize = 6   
   icon= minusIcon 

The fontSize is necessary, otherwise the buttons will still be big.