A custom CellRenderer in Lemur (Listbox)

Hi @pspeed, regarding to our talk here:

Basically I do have an Listbox, that is a Grid with X and Y number of attached cells. Not all of them are necessary visible.

The content of the cell in Listbox is written in that line. (My modified Listbox looks a bit different. Original Listboxs model is one-dimensional (row), while I have two dimensions (row and column)
But however in both cases CellRenderer.getView is called

  @Override
    public Panel getView( T value, boolean selected, Panel existing ) {
        if( existing == null ) {
            existing = new Button(valueToString(value), elementId, style);
        } else {
            ((Button)existing).setText(valueToString(value));
        }
        return existing;
    }
}

So in the end the Button that is attached to the Grid(Listbox) is - as you said in the WIP threat - able to take picture and text.

In order to get a specific picture attached we need to add a line
existing = new Button(Background choosen picture);

So the issue is: The picture has to be available in the Assetlist therefore it has to be loaded.

        Texture txt = getApplication().getAssetManager().loadTexture("/Pictures/MyPic.png");
        QuadBackgroundComponent qC= new QuadBackgroundComponent(txt);

How can I have the pics available ?
There is for sure some ways to do it. Thats how I thought it is less invasive:
CustomCellRenderer extents CustomcellRenderer
it will need a

  • private ArrayList<String[]> contents = new ArrayList<String[]>(); = A list of 3 variables (row, column, Adress or name of the picture)

    • AssetManager

I am not sure what is best now. Loading the picture and creating the Button whenever the Listbox is refreshed or is it better to prepare the Buttons before. Since in my project I have a Style Appstate I am currently thinking to go with that approach.
When the CustomCellRenderer is created I do

attrs = style.getSelector(new ElementId("exit").child("button"), "style");
Texture txt = getApplication().getAssetManager().loadTexture("/Pictures/MyPic.png");
QuadBackgroundComponent qC= new QuadBackgroundComponent(txt);
attrs.set("background",qC);

While refreshing the Listbox all I have to do is

  if (cell == the correct cell) {
            existing = new Button("", new ElementId("exit").child("button"));
 else 
make sure background is removed and otherwise  existing = super.getView(value, selected, existing);
}

But how do I know what is the correct cell ? There is several options as well. First abusing the (String) content of the value and check that out. Or and thats what I thought.
Overwrite protected Panel getListCell( int row, int col, Panel existing ) from Listbox and check that (Array)List from the CellRenderer if it has to draw a picture or just set a text.

→ For that reason I thought I extend Listbox and CustomCellRenderer to not mess around with them (what I could by doing things like if CellRenderer instance of…) for some specific use case

Questions:
If I set that style (for all the possible buttons) how can I remove it from style e.g. on closing the Listbox ?
Is that aproach logical/usefull at all or is there just an easy way

First WIP edit

  • will probably use a Map<K,V> and not an ArrayList<String[]>

What are in your multiple columns? I was commenting on the pic you showed before that seemed like every line had an image and text. (Edit: and Button/Label already support images and text together.)

If your list box model is just List<String> then I suspect you are not using things to their full potential. What are the actual objects that you are showing in the list? What is in the multiple columns?

Some of the questions you’ve asked seem like maybe you’ve painted yourself into a corner and now are trying to figure out how to levitate out of the room… when perhaps there was a different way to paint, so to speak.

1 Like

I think I got what you try to pointing me to.
→ All my cells are set (as in “original” Listbox) as “<T”>.

So I could set the “desired” Button directly in Listbox and just need a small adjustement in the CellRenderer to distinguish between Text and a final button

grafik

Good point - This is what happens when you program very rarely…

That’s not really what I mean.

Since I’m working completely in the dark, I will provide an example.

Let’s say you want to list player inventory with the name and an icon representing that item.

The relatively naive approach would to have ListBox<String> and do some weird magic to figure out the icon.

But it would be even better to have ListBox<GameItem>, ie: a list of the actual inventory items.

Then the cell renderer can look at that GameItem and get whatever information it needs to set the image on the label, set the text, put the image on the right or the left, add additional annotations. It has access to all of the properties of GameItem to figure out how to display the game item.

1 Like

I got you. Sorry If I am unclear. But thats

what I will do and was planning to do.
The Listbox Model will contain that ListBox and the custom CellRenderer will have to know what to do with it.
But it needed your hint otherwise I had went the way with ListBox or even worse…

Thx.

@pspeed

Just coming back to this.
Yes, the approach from the WIP threat in the end means you store everything in your database (listbox).
If we think of a list where you can choose a unit from, that would maybe mean 5 pictures (e.g. 5 different cars) and some text per item.
But if we use the same list to show user statistics and you have 5.000 users that would mean 5.000 avatares + 5.000 x some text items (user name, scores etc.).

In the second case that would mean at least 5.000 pictures (Buttons with background pictures) are stored. If whole models (avatars) are stored it would be 5.000 scenes with e.g… (user) models

A better approach would be to store some custom information to the Listbox.
most easy thing would be e.g. String
Example: String S1 = {“UserPicture”,"Link_to_that_picture}
String S2 = {“UserAvatare”,"Link_to_the_model}
In Listbox one would store e.g.
List item 1 = String “Text”
List item 2 = S1
List item 3 = S2

The Cellrenderer then would need to know
list item 1 = use default with existing = new Button(valueToString(value), getElementId(), getStyle());

list item 2 = “Ah its a user picture so do”

Texture txt = getApplication().getAssetManager().loadTexture("Link_to_that_picture");
Button X = new Button("");
X.setBackground(new QuadBackgroundComponent(txt));
existing = X;

list item 3 = “Ah its a user avatar so do”

Spatial Model = assetManager.loadModel("Link_to_the_model");
prepareScene with light, tweens etc. e.g. in a ViewPortpanel -> we call that avatarobject
existing = avatarobject;

The second approach would use much less memory/objects that are stored (e.g. only Strings), the CellRenderer would at least need to have an access to AssetManager.
The first approach would eat a lot of memory, while it does not require the object making during gridrefresh or any access to assetManager etc.

→ What you described here would be the second approach ? You make a Gameitem object. Cell renderer then, depending on an itemid or a name etc., would display it but not (as in the first approach) take the “ready already” item from the storage ?

What about realy realy huge lists e.g. with several million lines → I am sure even with only strings it would make sense to only load like 500 at once to the listbox ?

Yes, most of the time a game will already have a Gameitem object that it uses for other things. If it’s player inventory then there is already some kind of game object used for tracking inventory. (At least I hope the game isn’t keeping 100 sword, arrow, potion, etc. Spatials in a list somewhere…) Some of those inventory items might be “stacks” rendered with little numbers in the corner. Some might regular objects. Some might be animated buffs. Whatever. The renderer decides based on the object how to render them.

I don’t know what you are actually displaying to your user and you’ve dodged the question a couple of times now. So I’m just going to have to keep making up examples that I hope are similar.

Load them from where? What are you already keeping in RAM?

However you are already managing a million items can already work with ListBox if you have a proper lazy List<GameItem> that only loads what is asked.

…but whatever the case, normally ListBox is only creating the visible GUI elements. If there are only 10 items visible then only 10 Panels exist. Storing Buttons right in the List defeats this because you’ve already precreated every GUI element.

Better to have actual model objects (as in Model-View-Controller MVC model, not as in Spatial model) in the List… then create the list item GUI elements as needed. (Also, the reason the cell renderer gets passed the existing list item GUI element is so that it can reuse it if possible instead of creating new buttons every time or whatever. So in my example above, if the cell renderer sees that value is a stack of arrows and the existing item is already the StackLabel then it can just change the paramaters rather than creating a new StackLabel.)

Again, it would save me a lot of time if I knew, from the user’s perspective, what was actually being displayed. Then I wouldn’t have to constantly spend time making up scenarios and hoping they match your use-case.

This is the closest that we’ve gotten but I don’t know if they are real or just examples.

But let’s say you have User objects and you will load these from rows in an SQL database… you might have a million users in the database.

public class UserList extends AbstractList<User> {
    ...database related connections, prepared statements, etc...

    @Override
    public int size() {
        return sql query to count the rows
    }

    public User get( int index ) {
        ...get the data from row 'index'
        create the User object from it
        return the User object.
    }
}

Without trying to get too complicate in my made up example, let’s say there are two kinds of displays for a user. Regular users will get a regular Button but maybe special Users will get a SpecialUserPanel which is custom GUI element with some other GUI elements for access level or something.

class UserCellRenderer implements ValueRenderer<User> {
    ...asset manager... whatever
    ...
    
    
    @Override
    public Panel getView( User user, boolean selected, Panel existing ) {
        // Load user avatars, icons, whatever
            
        if( user.isSpecialUser() ) {
            if( existing instanceof SpecialUserPanel ) {
                SpecialUserPanel result = (SpecialUserPanel)existing;
                ...set the special panel stuff
                return result;
            } else {
                SpecialUserPanel result = new SpecialUserPanel(...data for panel);
                ...set any additional stuff
                return result;
            }  
        } else {
            Button result;
            if( existing instanceof Button ) {
                result = (Button)existing;
            } else {
                result = new Button(...);
            }
            ...set the text and icon on the Button as appropriate
            return result;  
        } 
    }
}
UserList users = ...
VersionedList<User> listModel = new VersionedList<>(users);
ListBox<User> userListBox = new ListBox<>(listModel);
userListBox.setCellRenderer(new UserCellRenderer(asset manager, whatever it needs));

That’s the basic idea. Each of those areas from the user list to the renderer to the special user panel would have to be fleshed out more. If only 10 items are visible then only 10 Buttons/SpecialUserPanels are ever created at any one time… and probably only 10 User objects are loaded from the database.

And really, it’s super strange to have different GUI elements in a list rather than one type of GUI element that just gets configured slightly different but you can see that it is possible.

2 Likes

Thanks, that helps a lot.

And sorry for doging… maybe in future I will try to have a game, thats multiplayer and will have databases in background etc. but wit the current speed I might finish in 10 or 20 years only or it will never make it behind first small concepts.

The thing is (spoiler: general JME lamenting): I could not cope with nifty and thought (and still think) lemur is the most mature GUI available. Unfortunateley some things are lacking and even if some users have implemented stuff (that ViewPort panel is not mine). When I started I said I “need” things like multicolumn Listbox with multiselect, textfields with scrolling, select etc.
Given the fact I never learned coding and never worked in that field (probably never will) its kind of a hobby for me. So recently (with some of your lemur 1.16 updates) I realized (another time) that I probably did some sh*t stuff in my modifications. E.g. Before you mentioned the VersionedList stuff above I said to myself (like 2 days ago) damn I should have changed the VersionedList (model) in the Listbox class insteadt of what I had done (already in 2017/2018).

So whenever I ask about concepts or more abstract things it is
a) to improve my stuff and the way I should do things (or not to do)
b) I hope that there is other users that try to learn from forum and that also might be new to or unfamiliar with the theories (like those students that visit from time to time). I learned a lot from the forum, so I hope that discussion will help others as well or can make it one day to (lemur) best practices :slight_smile:

Small edit:
Basing on that discussion I would say: What I have done (more or less) is to have my custom VersionedList that stores the “GameItems”. It can do that by custom value. I also have a CellRenderer that is able to decide and to know what has to be displayed in the ListBox. I will try to avoid storing ready Buttons/models etc. (even if I know that it is possible).
Mentioning the reuse of existing cells - I will try to work on that and reuse the existing cells more.