[Solved] [Lemur] Fullscreen background?

Pretty simple question about something I most likely missed in the docs.

How would I have a fullscreen background in Lemur?

I though of wrapping everything in a container and setting the background to the container, but the container is only the size of the contents, as well as the background.
I’d set the height and width to the screen, but I can’t find any way to set the height/width of a panel.

I’m styling in groovy, so styling help is preferred in groovy.

Thank you

I think I can accomplish this by setting all the gui nodes to a common spatial and moving them via setLocation, but then how do I handle Z-order?

Easiest way:
container.setPreferredSize(new Vector3f(camera.getWidth(), camera.getHeight(), 1));

In styling it’s not so simple. The biggest question for styling would be “Which camera?” “Which viewport?” etc… styling can’t know where you will display the component. Only you know that.

If you want your panel/container to automatically resize if the window size changes then it’s a little trickier. JME does not provide a good way to detect this but there are sneaky ways by adding a scene processor.

I’ve made modifications to SiO2’s DebugHudState so that it keeps the full-sized container sized to the camera even if the window size changes. It’s not released in a real version yet but you can see how I did that here:

But for just one time setting a panel, it’s as easy as setting its preferred size.

1 Like

Very much appreciated! Thank you for that

2 Likes

One quick question before I close this out. I now have everything centered and fullscreen, but now my menu is gone! I’m 99% sure its hiding behind the background image.

I have the style like so (Note: the rest of the style is word for word glass-style.groovy with this line added above selector ("glass")):

def backgroundZOffset = -1f;
def spaceBackground = new IconComponent( "Interface/Background/Spacebackground.png", 1f, 0, 0, backgroundZOffset, false );
selector("backgroundImage") {
	background = spaceBackground
}

You can see I’ve been playing around with the z offset, but no luck so far.
I know the style is being applied because the image displays.
All other UI elements are children of this one, which would make me think that layer expansion (here) would have the background behind the content since its on the elements parent.

Here’s an abstraction of the UI layers:

rootNode 
    The node all other ui elements get attached to. 
    This gets attached to the GuiNode. 
    This also carries the background image
    Is a 'Container'
    Style = "backgroundImage"
titleNode
    Attached to rootNode
    Simply title text
    Is a 'Label'
    Style = "title"
buttonContainer
    Attached to rootNode
    Holds buttons
    Is a 'Container'
    Style = default ("glass")
start/settings/quit button
    Attached to buttonContainer
    Self explanatory
    Are 'Button's
    Style = default ("glass")

All of the displayed fine until the addition of the background to the ‘rootNode’

I’m probably lacking critical information here, but how do I get the z-ordering right here?
Any help greatly appreciated!

Here are the pictures showing whats going on.
You can see the one with the background even covers the debug display

Edit well somehow imgur cut off the bottoms of the images, so you can’t see the debug display in either. But it’s in the one with the menu items showing, and gone when the background is there

All of the UI elements in discussion are child of the container with the background image?

Do you squash Z in any preferred sizes?

This is hard to debug without seeing code.

BTW, I think you might be using ‘style’ incorrectly in this case… and probably mean element ID.

Also, something in my brain tickles that the zOffset of the background components like QuadBackgroundComponent are already offsetting ‘behind’ when positive. I can’t be sure without digging a little deeper but you might try flipping the sign on your backgroundZOffset so that it’s positive and see if that fixes things.

Barring that, a code example illustrating the problem could be useful.

Sorry for the delayed response! And yes, all the UI elements are direct children of the container with the background image, minus the buttons, they are children of buttonContainer, which is then a child of the background container.

And I haven’t honestly touched (or thought of) z ordering until I tried this.

I definitely agree about needing to see code, it’s just my code is a bit all over the place. I’ll make a working example, give me bout 10 minutes.

And I’m sorry, the abstraction I made was confusing. Style = "backgroundImage" meant that when I created the panel, I set that as the style by passing it into the constructor.
I will also try flipping the sign and see if that works, but I vaguely remember seeing in the source that zOffset get thrown into FastMath.abs.
Either way I’ll be back in a few with some code and more tests

Here’s a minimal example:

import com.jme3.app.SimpleApplication;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.simsilica.lemur.Button;
import com.simsilica.lemur.Command;
import com.simsilica.lemur.Container;
import com.simsilica.lemur.GuiGlobals;
import com.simsilica.lemur.Label;
import com.simsilica.lemur.component.IconComponent;
import com.simsilica.lemur.component.SpringGridLayout;
import com.simsilica.lemur.style.BaseStyles;

public class Test extends SimpleApplication {
    public static void main( String... args ) {
        Test main = new Test();
        main.start();
    }        
    protected String styleLocation = "Interface/Style/base.groovy";
    protected String defaultStyleName = "base";
    protected String title = "Title";
    protected String startButtonText = "Start";
    protected String settingsButtonText = "Settings";
    protected String quitButtonText = "Quit";
    protected String backgroundImageLocation = "Interface/Background/Spacebackground.png";
    protected Vector2f backgroundImageMargin = new Vector2f(0,0);
    protected float backgroundImageScale = 1f;
    protected Container rootNode;
    protected IconComponent backgroundImage;
    protected Label titleLabel;
    protected Container buttonContainer;
    protected Button startButton;
    protected Button settingsButton;
    protected Button quitButton;
    @Override
    public void simpleInitApp() {
        GuiGlobals.initialize(this);
        BaseStyles.loadStyleResources(styleLocation);
        GuiGlobals.getInstance().getStyles().setDefaultStyle(defaultStyleName);
        
        
        Vector3f cameraDimensions = new Vector3f(getCamera().getWidth(), getCamera().getHeight(),0);
        Vector3f cameraCenter = cameraDimensions.mult(0.5f);
        backgroundImage = new IconComponent( backgroundImageLocation, backgroundImageScale, backgroundImageMargin.x, backgroundImageMargin.y, 1f, false );        
        
        //Top sets background image here from code
//            rootNode = new Container(new SpringGridLayout());
           //Uncomment above and comment below to disable background image and see menu
//            rootNode.setBackground(backgroundImage);
        //!Top
        
        //Bottom sets background image from style
            rootNode = new Container(new SpringGridLayout(),"backgroundImage");
        //!Bottom
        
        
        Vector3f pco = rootNode.getPreferredSize().mult(0.5f);
        Vector3f trans = new Vector3f(cameraCenter.x - pco.x, cameraCenter.y + pco.y ,0);
        rootNode.setLocalTranslation(trans);
        rootNode.setPreferredSize(new Vector3f(getCamera().getWidth(), getCamera().getHeight(), 1));
        titleLabel = new Label(title,"title");
        rootNode.addChild(titleLabel);
        buttonContainer = new Container(new SpringGridLayout());
        startButton = new Button(startButtonText);
        buttonContainer.addChild(startButton);
        settingsButton = new Button(settingsButtonText);
        buttonContainer.addChild(settingsButton);
        quitButton = new Button(quitButtonText);
        quitButton.addClickCommands((Command<Button>) (Button source) -> {
            stop();
        });
        buttonContainer.addChild(quitButton);
        rootNode.addChild(buttonContainer);        
        getGuiNode().attachChild(rootNode);
    }    
}

And here’s the style (glass-style.groovy but “glass” was replaced by “base” and a couple selectors added):

import com.simsilica.lemur.*;
import com.simsilica.lemur.Button.ButtonAction;
import com.simsilica.lemur.component.*;

def gradient = TbtQuadBackgroundComponent.create( 
                                        texture( name:"/com/simsilica/lemur/icons/bordered-gradient.png", 
                                                 generateMips:false ),
                                                 1, 1, 1, 126, 126,
                                                 1f, false );

def bevel = TbtQuadBackgroundComponent.create( 
                                        texture( name:"/com/simsilica/lemur/icons/bevel-quad.png", 
                                                 generateMips:false ),
                                                 0.125f, 8, 8, 119, 119,
                                                 1f, false );
 
def border = TbtQuadBackgroundComponent.create(
                                        texture( name:"/com/simsilica/lemur/icons/border.png", 
                                                 generateMips:false ),
                                                 1, 1, 1, 6, 6,
                                                 1f, false );
def border2 = TbtQuadBackgroundComponent.create(
                                        texture( name:"/com/simsilica/lemur/icons/border.png", 
                                                 generateMips:false ),
                                                 1, 2, 2, 6, 6,
                                                 1f, false );
 
def doubleGradient = new QuadBackgroundComponent( color(0.5, 0.75, 0.85, 0.5) );  
doubleGradient.texture = texture( name:"/com/simsilica/lemur/icons/double-gradient-128.png", 
                                  generateMips:false )

def spaceBackground = new IconComponent( "Interface/Background/Spacebackground.png", 1f, 0, 0, -1f, false );
                                  
selector( "base" ) {
	font = font("Interface/Fonts/menu.fnt");
    fontSize = 20
}
selector("backgroundImage") {
	background = spaceBackground
}

selector("title") {
	font = font("Interface/Fonts/menu-large.fnt");
    fontSize = 50	
}
 
selector( "label", "base" ) {
    insets = new Insets3f( 2, 2, 0, 2 );
    color = color(0.5, 0.75, 0.75, 0.85)     
}

selector( "container", "base" ) {
    background = gradient.clone()
    background.setColor(color(0.25, 0.5, 0.5, 0.5))
}

selector( "slider", "base" ) {
    background = gradient.clone()
    background.setColor(color(0.25, 0.5, 0.5, 0.5))
} 

def pressedCommand = new Command<Button>() {
        public void execute( Button source ) {
            if( source.isPressed() ) {
                source.move(1, -1, 0);
            } else {
                source.move(-1, 1, 0);
            }
        }       
    };
    
def repeatCommand = new Command<Button>() {
        private long startTime;
        private long lastClick;
        
        public void execute( Button source ) {
            // Only do the repeating click while the mouse is
            // over the button (and pressed of course)
            if( source.isPressed() && source.isHighlightOn() ) {
                long elapsedTime = System.currentTimeMillis() - startTime;
                // After half a second pause, click 8 times a second
                if( elapsedTime > 500 ) {
                    if( elapsedTime - lastClick > 125 ) {  
                        source.click();
                        
                        // Try to quantize the last click time to prevent drift
                        lastClick = ((elapsedTime - 500) / 125) * 125 + 500;
                    }
                } 
            } else {
                startTime = System.currentTimeMillis();
                lastClick = 0;
            }
        }       
    };     
    
def stdButtonCommands = [
        (ButtonAction.Down):[pressedCommand], 
        (ButtonAction.Up):[pressedCommand]
    ];

def sliderButtonCommands = [
        (ButtonAction.Hover):[repeatCommand]
    ];

selector( "title", "base" ) {
    color = color(0.8, 0.9, 1, 0.85f)
    highlightColor = color(1, 0.8, 1, 0.85f)
    shadowColor = color(0, 0, 0, 0.75f)
    shadowOffset = new com.jme3.math.Vector3f(2, -2, -1);
    background = new QuadBackgroundComponent( color(0.5, 0.75, 0.85, 0.5) );
    background.texture = texture( name:"/com/simsilica/lemur/icons/double-gradient-128.png", 
                                  generateMips:false )
    insets = new Insets3f( 2, 2, 2, 2 );
    
    buttonCommands = stdButtonCommands;
}


selector( "button", "base" ) {
    background = gradient.clone()
    color = color(0.8, 0.9, 1, 0.85f)
    background.setColor(color(0, 0.75, 0.75, 0.5))
    insets = new Insets3f( 2, 2, 2, 2 );
    
    buttonCommands = stdButtonCommands;
}

selector( "slider", "base" ) {
    insets = new Insets3f( 1, 3, 1, 2 );    
}

selector( "slider", "button", "base" ) {
    background = doubleGradient.clone()
    background.setColor(color(0.5, 0.75, 0.75, 0.5))
    insets = new Insets3f( 0, 0, 0, 0 );
}

selector( "slider.thumb.button", "base" ) {
    text = "[]"
    color = color(0.6, 0.8, 0.8, 0.85)     
}

selector( "slider.left.button", "base" ) {
    text = "-"
    background = doubleGradient.clone()
    background.setColor(color(0.5, 0.75, 0.75, 0.5))
    background.setMargin(5, 0);
    color = color(0.6, 0.8, 0.8, 0.85)
         
    buttonCommands = sliderButtonCommands;
}

selector( "slider.right.button", "base" ) {
    text = "+"
    background = doubleGradient.clone()
    background.setColor(color(0.5, 0.75, 0.75, 0.5))
    background.setMargin(4, 0);
    color = color(0.6, 0.8, 0.8, 0.85)
         
    buttonCommands = sliderButtonCommands;
}

selector( "slider.up.button", "base" ) {
    buttonCommands = sliderButtonCommands;
}

selector( "slider.down.button", "base" ) {
    buttonCommands = sliderButtonCommands;
}

selector( "checkbox", "base" ) {
    def on = new IconComponent( "/com/simsilica/lemur/icons/Glass-check-on.png", 1f,
                                 0, 0, 1f, false );
    on.setColor(color(0.5, 0.9, 0.9, 0.9))
    on.setMargin(5, 0);
    def off = new IconComponent( "/com/simsilica/lemur/icons/Glass-check-off.png", 1f,
                                 0, 0, 1f, false );
    off.setColor(color(0.6, 0.8, 0.8, 0.8))
    off.setMargin(5, 0);
    
    onView = on;
    offView = off;    

    color = color(0.8, 0.9, 1, 0.85f)
}

selector( "rollup", "base" ) {
    background = gradient.clone()  
    background.setColor(color(0.25, 0.5, 0.5, 0.5))
}

selector( "tabbedPanel", "base" ) {
    activationColor = color(0.8, 0.9, 1, 0.85f)
}

selector( "tabbedPanel.container", "base" ) {
    background = null
}

selector( "tab.button", "base" ) {
    background = gradient.clone()
    background.setColor(color(0.25, 0.5, 0.5, 0.5))
    color = color(0.4, 0.45, 0.5, 0.85f)
    insets = new Insets3f( 4, 2, 0, 2 );
    
    buttonCommands = stdButtonCommands;
}

Interestingly enough, inverting the Z order didn’t change anything, but when refactoring all this to post, the debug display is now properly displaying over the background, but the menu is still hidden.
I also made a way in the code to easily switch between setting the background from style and setting it in code, both with the same results.
The background image in question is just a 4000x2000 image in blender with some random “stars” and “nebulae”, made much bigger than any expected screen so it fills any screen without special adjustments, so any big image should replace it fine, but really any image should be fine.
The font is Mongolian Baiti at image size 512, font size 40, and letter spacing 2, from the sdk font generator

Without being able to run this myself yet…

What happens if you change that -1 to 1?

The only other maybe problematic thing I saw was:

Since your rootNode is actually a whole bunch of components, it’s quite possible that that z=1 in preferred size is not enough to contain everything and so things get weird.

You could try setting it to a bigger value but I guess the “correct” way would be to use the z you get from the getPreferredSize() earlier (before multiplying it by 0.5).

What I meant by my style comments earlier is that “style” is meant for the overall style of the UI and usually its used by every GUI element within a particular UI. You are using it like an ElementId which I guess is ok, just not the normal way.

rootNode = new Container(new SpringGridLayout(), new ElementId("backgroundImage"), null);

But personally, most of the time I wouldn’t put the whole UI’s background image into the style. I’d just set it on the Container when I create it.
…nothing wrong with what you’ve done. Just unconventional.

…as you have commented out is the more typical way.
And I guess that one already tested zOffset=1… which I still think is the right way. So try that and the increased z in preferred size.

Another tip, if you are going to use mostly glass style with just your own tweaks, you can just make a resource file in your project in the same path+name and the styling system will pick it up automatically when you load the glass style. So you could just have the parts that are different. It’s fine to fork it too if you will be using it as a base to change a bunch of stuff… just pointing it out.

1 Like

Much appreciated!

I tried flipping the signs on that zOffset, it didn’t change the result

Do you think it would be safe to set z = (number of children * 1)?

That sounds like a plan, but I have one question regarding that:
Should I get/set the preferred size before or after I add the children?
I assume adding children would change the “preferredSize”, is that correct?

Gotcha, I was going for the “ElementId” approach, but couldn’t grasp it. I’m fluent in css, so I guess that felt natural to me. How would I implement the same with ElementId?

I rather agree, it felt wrong. I think I’ll stick with setting it in code, I feel it will be much more flexible later on. Thank you!

I appreciate the pointer! I might do that while I figure out the UI, but eventually I will have to make my own style as glass doesn’t fit my games theme.

I’ve refactored everything based on your suggestions, including increasing zSize and setting the background in code, and still only get background.

Looking at the Getting Started Component Layer Breakout section, it would appear that the background, being an IconComponent, pushes the content aside itself (Note how the text layer is pushed aside the icon layer).

Is this the case? And if so, am I using the wrong type of Container layout?
And would I also be able to get the rest of the content back on-screen by setting their setLocalTranslation?

That’s true. IconComponent is not meant for backgrounds. It’s meant to put icons in labels and stuff.

Probably what you want is QuadBackgroundComponent.

As for Z size, you want to setup your container like you want it… then get the preferred size. Keep the z. Set the x,y to camera width/height and set it back.

1 Like

As for styling, it’s similar enough to CSS to be confusing but fundamentally they are different.

The style is meant to control the overall look and feel of your UI. For example, you could make a “stone” or “retro” style and if it supported all of the standard elements you could swap it out for glass and instantly get a different “skin”.

ElementId otherwise incorporates much of CSS selector ability including what CSS would call “style”. But because there is no concept of parent/child at the style level (at the time of styling, GUI elements won’t have a parent or children, for example) then things work significantly different. Basically, the idea of the hierarchy is built into the dot notation of the ID.

So “slider.button” will inherit defaults from ‘slider’ and defaults from ‘button’ but you can also customize settings for ‘slider.button’ itself. And actually, in the case of slider it’s slider.thum.button, slider.up.button, slider.down.button so you can hit (slider, button) or slider.up.button, etc…

Some of this and precedence are described here: Styling · jMonkeyEngine-Contributions/Lemur Wiki · GitHub

1 Like

Gotcha, that makes sense, the background is the only thing showing because it “pushed” everything else out of the way.

Gotcha. But I noticed that QuadBackgroundComponent takes a Texture rather than a String resource location. So I have to load the image as a texture first right?

Copy that, thank you for all the golden information!

Gotcha, very much appreciated!

Yes,

assetManager.loadTexture(backgroundImageLocation);

…easy as that.

1 Like

Gotcha, much appreciated! And I want to say thank you for all you’ve done on this engine! This is really good work you’re doing, once I get some extra cash in I’m gonna have to make a donation, you guys more than earned it!

2 Likes