Lemur File browser

I am creating a file/folder browser with lemur.

If this already exists, please post a link.

Else, I will post code for it here.

I don’t know if anyone has created one yet. Writing one is on my to-do list but fairly low down. Today when I need file browsing I cheat and open a JFileChooser. (see SimArboreal Editor. :slight_smile: )


It’s a bit crude but it works. Pictures will only show if they are inside the asset folder.

Hope you enjoy!


Call this to open it:

FileBrowser.init(System.getProperty("user.dir"), new Callable<Object>() {
                public Object call() {
                    return OnTextureChosen();

And have the method “OnTextureChosen” look something like this:

public static Object OnTextureChosen() {         
    if(FileBrowser.isCanceled()) return null;        
    Texture t = AssetManager.loadTexture(FileBrowser.getAssetPath());        
    return null;

The code:

package game.lemur;

import com.jme3.input.event.MouseButtonEvent;
import com.jme3.input.event.MouseMotionEvent;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Spatial;
import com.jme3.texture.Texture;
import com.simsilica.lemur.Button;
import com.simsilica.lemur.Command;
import com.simsilica.lemur.Container; 
import com.simsilica.lemur.Label;
import com.simsilica.lemur.event.CursorEventControl;
import com.simsilica.lemur.event.DragHandler;
import com.simsilica.lemur.TextField;
import com.simsilica.lemur.component.TbtQuadBackgroundComponent;
import com.simsilica.lemur.event.MouseEventControl;
import com.simsilica.lemur.event.MouseListener;
import com.simsilica.lemur.geom.TbtQuad;
import game.Jme;
import java.io.File;
import java.util.concurrent.Callable;

 * @author Eric Bourque
public class FileBrowser {
private static boolean canceled = false;
public static boolean isCanceled() {
    return canceled;

private static boolean done = false;
public static boolean isDone() {
    return done;

private static String selectedPath;
public static String getSelectedPath() {
    return selectedPath;

private static String assetPath;
public static String getAssetPath() {
    return assetPath;

private static Container window;
private static String currentFolder;    

private static Label selected;
private static Callable<Object> endCall;

public FileBrowser(){


public static void show(){

public static void hide(){
private static void gotoParentDir(){
    File f = new File(currentFolder);  
    if(f.getParentFile() == null)return;
    currentFolder = f.getParentFile().getPath()+"\\"; 

private static void gotoChildDir(String child){
    child = child.substring(2);             
    currentFolder = currentFolder +child+"\\"; 

private static void refresh(){
    Vector3f pos = window.getLocalTranslation();
    init(currentFolder, endCall);

public static void init(String path, Callable<Object> func){
    endCall = func;
    currentFolder = path;
    window = new Container();  
    CursorEventControl.addListenersToSpatial(window, new DragHandler());
    Container c = window.addChild(new Container()); 
    c.addChild(new Label("File Selector 1.0"));
    TextField tf = c.addChild(new TextField(LemurConst.style));
    if(selectedPath != null){
            int i = selectedPath.indexOf("assets")+7;           
            assetPath = selectedPath.substring(i).replace("\\", "/");   

            if(selectedPath.endsWith(".png") || selectedPath.endsWith(".jpg") || selectedPath.endsWith(".bmp")){    
                Container imagePreview = c.addChild(new Container());             

                System.out.println("AssetPath: " + assetPath);  
                Texture t = Jme.assetManager().loadTexture(assetPath);
                TbtQuadBackgroundComponent tbt = new TbtQuadBackgroundComponent(new TbtQuad(200, 200), t);
                imagePreview.setPreferredSize(new Vector3f(200,200,0));   
        }catch(Exception e){}
    Button b = c.addChild(new Button("Back"));
    b.addClickCommands(new Command<Button>() {
        public void execute( Button source ) {            
    c = window.addChild(new Container()); 
    File folder = new File(currentFolder);
    String[] list = folder.list();
    for (int i = 0; i < list.length; i++) { 
        File file =  new File(path + list[i]);                            
        if (file.isDirectory()) {                                    
           //Label l = ;
           addClickListener(c.addChild(new Label("./" + list[i])), true);
           //CursorEventControl.addListenersToSpatial(l, new CursorMotionEvent());
    for (int i = 0; i < list.length; i++) { 
        File file =  new File(path + list[i]);                            
        if (!file.isDirectory()) {         
            Label l = c.addChild(new Label(list[i]));
            if(selectedPath != null && selectedPath.equals(currentFolder + list[i])){
                if(selected != null)
                    selected.setColor(new ColorRGBA(0.5f, 0.75f, 0.75f, 0.85f)); //glass
                selected = l;
            addClickListener(l, false);
    b = c.addChild(new Button("Ok"));
    b.addClickCommands(new Command<Button>() {
        public void execute( Button source ) {            
            done = true;
            }catch(Exception e){}
    b = c.addChild(new Button("Cancel"));
    b.addClickCommands(new Command<Button>() {
        public void execute( Button source ) {            
            canceled = true;
            }catch(Exception e){}

private static void addClickListener(Spatial s, boolean isFolder) {
            new MouseListener() {
        public void mouseButtonEvent(MouseButtonEvent mbe, Spatial target, Spatial capture) {
            selectedPath = "";
            if(target != capture)return;                
                selectedPath = currentFolder +((Label)capture).getText();
                System.out.println("Selected path: "+selectedPath);                

        public void mouseEntered(MouseMotionEvent mme, Spatial target, Spatial capture) {            
            if((Label)target != selected)

        public void mouseExited(MouseMotionEvent mme, Spatial target, Spatial capture) {   
            if((Label)target != selected)
                ((Label)target).setColor(new ColorRGBA(0.5f, 0.75f, 0.75f, 0.85f)); //glass

        public void mouseMoved(MouseMotionEvent mme, Spatial target, Spatial capture) {

I would like to improve this with a scrolling window but I don’t know how yet…


Just finished! Really hope you like it, tell me what you think

Heheh… glad you got it working. It’s very… ‘functional’. :smile:

I have to go but later tonight I may give some tips on neatening up your UI a bit if you are interested.

Sure! Did it in 3 hours so it’s bound to need a bit of that

If this is something many may want to use, what is the best way for me to make it publicly available?

I think it might be better to organize it as a series of subpanels.

So something like the following which I’ll do in a pseudo-notation that hopefully you can follow:

container(BorderLayout) {
    north: container(SpringGridLayout(Axis.X, Axis.Y)) {
        label(path), button(back)
    center: container(SpringGridLayout(Axis.X, Axis.Y)) {
        listBox(files), label(imagePrevies)
    south: container(SpringGridLayout(Axis.X, Axis.Y)) {
        button(ok), button(cancel)

Note the spring grid layouts that reverse the axis order so that by default children go into new columns instead of new rows.

Also, if you let the user specify their own Actions when creating the file selector then you can just use ActionButtons in the bottom panel and the caller can then 100% define what the Ok and Cancel (or any number of buttons) do. Though I guess that’s tricky if you us “Ok” for drilling into directories, also.

Anyway, those are my random thoughts. If I ever do a real version then it will be quite a bit different as I’ll use a grid or table for the file list instead of a listbox, etc… Your approach is the easiest way to get the most functionality for the effort, though.

The OK button is for validating the final selection. Folders are entered by left clicking a directory. (dirs have a ./ infront)

Label color changes on mouse over and stay white if clicked.

I send a callable method to initialise the window so that the user can decide what the method the ok button does when clicked.

The downside of my browser is that there is a tiny gap inbetween labels where nothing gets selected but it should work better when the listbox is ready.

Thanks for the tips.

P.S. What timezone are you in? I’m in GMT+1

GMT-5 but I sleep at odd times.

You can already use ListBox… didn’t realize you weren’t.

Ok, will check it out…

I can’t run SimArboreal in Jme3.1 because I am missing librairies.

The link for all the ressources is broken or private: http://hub.jmonkeyengine.org/forum/topic/simarboreal-formerly-tree-editor/

import com.simsilica.arboreal.mesh.BillboardedLeavesMeshGenerator;
import com.simsilica.arboreal.mesh.SkinnedTreeMeshGenerator;
import com.simsilica.arboreal.mesh.Vertex;
import com.simsilica.builder.Builder;
import com.simsilica.builder.BuilderReference;

import org.progeeks.json.JsonParser;
import org.progeeks.json.JsonPrinter;

And this:

I really want to check out how it works

You need to also check out SimArboreal and Pager. Probably SimFX also.

The jars for meta-jb-json should be in the tree somewhere.

Edit: note, I’ve never ported this to 3.1 yet. To run it, you might be better off downloading the full exe distro.

Just made it work on 3.1, minus the shadows, video recordings and a few other pixies

Now I can learn how to use Lemur properly. Thanks allot! Great job btw

P.S. how many trees do you think I can spawn if I make like 10 diff models and use instancing? Are we talking thousands or 10s of thousands? will the Wind cause lag?

I took a look at property panel. Love it, …but still needs a bit of code to access public fields.

For example, I want slider to change color in a ColorRGBA.

This class has public floats a,r,g,b, but I guess the invoke only works for getters and setters right?

when I set this up:

properties.addFloatProperty("Amb_B", Player.currentSolarSystem.sun.amb.getColor(), "b", 0f, 2f, 0.1f);

I get:

  Caused by: java.lang.RuntimeException: No suche property:b on:class com.jme3.math.ColorRGBA

So, anyway to do this without adding a bunch more floats to control color?

Try addFloatField() instead of addFloatProperty().

1 Like

The wind won’t cause lag, really, as it’s down in the shader… shouldn’t make any difference.

Instancing gives back performance but it’s not perfect. The driver still has to do per-object setup so it’s not as free as say batching, but it does use a lot less memory.

In the IsoSurface demo you can see how it performs with a lot of trees. Even with LOD there is a limit.