Stats/FPS modification proposal

By default the Stats/FPS window is in the bottom left and up to now this didn’t really bother me, but I’m at the point where I want to implement a text notification area that I plan to place in that corner. Since I intend to have both available at the same time, at least during development, It’s really not optimal to have those overlapping. Readability would be awful.

The change I’m proposing is to have two new methods in SimpleApplication and several in StatsAppState.java.

Succinctly, you can call either

setStatsWindowLocation(x, y);

or

setStatsWindowLocation(intConstant);

The first is to position the stats at an arbitrary position. The second is for each of the corner of the screen being top-left, top-right, bottom-left and bottom-right. Values would be 0, 1, 2 and 3 respectively.

Here are the proposed changes.

SimpleApplication:
[java]
— Base (BASE)
+++ Locally Modified (Based On LOCAL)
@@ -265,6 +265,42 @@
}
}

  • /**

  • * Sets the location of the Stats window at a specific position on
    
  • * the screen. 
    
  • * 
    
  • * Notes:
    
  • * 
    
  • * The coordinate system is the same as the OpenGL
    
  • * screen coordinate (0, 0 is bottom left).
    
  • * 
    
  • * You are also responsible to check the validity of the values.
    
  • * 
    
  • * 
    
  • * @param x The X position on the screen.
    
  • * @param y The Y position on the screen.
    
  • */
    
  • public void setStatsWindowLocation(int x, int y) {

  •    stateManager.getState(StatsAppState.class).setStatsWindowLocation(x, y);
    
  • }

  • /**

  • * Sets the location of the Stats window at one of the predefined 
    
  • * positions on the screen. 
    
  • * 
    
  • * @param locType The screen corner location constant.
    
  • * 
    
  • * 0 = Top left.
    
  • * 1 = Top right.
    
  • * 2 = Bottom left (default).
    
  • * 3 = Bottom right.
    
  • * 
    
  • * 
    
  • */
    
  • public void setStatsWindowLocation(int locType) {

  •    stateManager.getState(StatsAppState.class).setStatsWindowLocation(locType);
    
  • }

  • public abstract void simpleInitApp();

    public void simpleUpdate(float tpf) {
    [/java]

StatsAppState
[java]
— Base (BASE)
+++ Locally Modified (Based On LOCAL)
@@ -58,6 +58,10 @@
private boolean showFps = true;
private boolean showStats = true;
private boolean darkenBehind = true;

  • private boolean locationChanged = true;

  • private int statsScreenLocation = -1;

  • private int screenPosX = -1;

  • private int screenPosY = -1;

    protected Node guiNode;
    protected float secondCounter = 0.0f;
    @@ -171,7 +175,7 @@
    fpsText = new BitmapText(guiFont, false);
    }

  •    fpsText.setLocalTranslation(0, fpsText.getLineHeight(), 0);
    

+// fpsText.setLocalTranslation(0, fpsText.getLineHeight(), 0);
fpsText.setText(“Frames per second”);
fpsText.setCullHint(showFps ? CullHint.Never : CullHint.Always);
guiNode.attachChild(fpsText);
@@ -188,7 +192,7 @@
app.getAssetManager(),
app.getRenderer().getStatistics());
// move it up so it appears above fps text

  •    statsView.setLocalTranslation(0, fpsText.getLineHeight(), 0);
    

+// statsView.setLocalTranslation(0, fpsText.getLineHeight(), 0);
statsView.setEnabled(showStats);
statsView.setCullHint(showStats ? CullHint.Never : CullHint.Always);
guiNode.attachChild(statsView);
@@ -201,17 +205,113 @@

     darkenFps = new Geometry("StatsDarken", new Quad(200, fpsText.getLineHeight()));
     darkenFps.setMaterial(mat);
  •    darkenFps.setLocalTranslation(0, 0, -1);
    

+// darkenFps.setLocalTranslation(0, 0, -1);
darkenFps.setCullHint(showFps && darkenBehind ? CullHint.Never : CullHint.Always);
guiNode.attachChild(darkenFps);

     darkenStats = new Geometry("StatsDarken", new Quad(200, statsView.getHeight()));
     darkenStats.setMaterial(mat);
  •    darkenStats.setLocalTranslation(0, fpsText.getHeight(), -1);
    

+// darkenStats.setLocalTranslation(0, fpsText.getHeight(), -1);
darkenStats.setCullHint(showStats && darkenBehind ? CullHint.Never : CullHint.Always);
guiNode.attachChild(darkenStats);
}

  • /**

  • * Set the StatsWindow location at an arbitrary location on the screen.
    
  • * 
    
  • * NOTE: The coordinate system is the OpenGL system.
    
  • * 
    
  • * @param x The X coordinate on the screen where to put the StatsWindow.
    
  • * @param y The Y coordinate on the screen where to put the StatsWindow.
    
  • */
    
  • public void setStatsWindowLocation(float x, float y) {

  •    statsScreenLocation = 4;
    
  •    screenPosX = (int) x;
    
  •    screenPosY = (int) y;
    
  •    locationChanged = true;
    
  • }

  • /**

  • * Set the StatsWindow location to one of the predefined location
    
  • * on the screen.
    
  • * 
    
  • * @param screenCorner The screen corner location constant.
    
  • * 0 = Top left.
    
  • * 1 = Top right.
    
  • * 2 = Bottom left (default).
    
  • * 3 = Bottom right.
    
  • */
    
  • public void setStatsWindowLocation(int screenCorner) {

  •    statsScreenLocation = screenCorner;
    
  •    locationChanged = true;
    
  • }

  • private void statsWindowLocation(int screenCorner) {

  •    int x;
    
  •    int yStats;
    
  •    int yFps;
    
  •    if (screenCorner  4) {
    
  •        screenCorner = 2;
    
  •    }
    
  •    int width = app.getGuiViewPort().getCamera().getWidth();
    
  •    int height = app.getGuiViewPort().getCamera().getHeight();
    
  •    int textWidth = 200;
    
  •    int statsHeight = (int) statsView.getHeight();
    
  •    int fpsHeight = (int) fpsText.getHeight();
    
  •    if (screenCorner == 0) {
    
  •        x = 0;
    
  •        if (showStats) {
    
  •            yStats = height - statsHeight;
    
  •            yFps = yStats - fpsHeight;
    
  •        } else {
    
  •            yStats = height - (int) statsView.getHeight();
    
  •            yFps = height - fpsHeight;
    
  •        }
    
  •    } else if (screenCorner == 1) {
    
  •        x = width - textWidth;
    
  •        if (showStats) {
    
  •            yStats = height - statsHeight;
    
  •            yFps = yStats - fpsHeight;
    
  •        } else {
    
  •            yStats = height - (int) statsView.getHeight();
    
  •            yFps = height - fpsHeight;
    
  •        }
    
  •    } else if (screenCorner == 2) {
    
  •        x = 0;
    
  •        yStats = (int) fpsText.getLineHeight();
    
  •        yFps = 0;
    
  •    } else if (screenCorner == 3) {
    
  •        x = width - textWidth;
    
  •        yStats = (int) fpsText.getLineHeight();
    
  •        yFps = 0;
    
  •    } else {
    
  •        x = screenPosX;
    
  •        yStats = screenPosY;
    
  •        yFps = screenPosY - fpsHeight;
    
  •    }
    
  •    setDarkenStats(x, yStats);
    
  •    setDarkenFPS(x, yFps);
    
  •    setStatsLocation(x, yStats);
    
  •    setFPSLocation(x, yFps + fpsHeight);
    
  •    locationChanged = false;
    
  • }

  • private void setDarkenStats(float x, float y) {

  •    darkenStats.setLocalTranslation(x, y, -1);
    
  • }

  • private void setStatsLocation(float x, float y) {

  •    statsView.setLocalTranslation(x, y, 0);
    
  • }

  • private void setDarkenFPS(float x, float y) {

  •    darkenFps.setLocalTranslation(x, y, -1);
    
  • }

  • private void setFPSLocation(float x, float y) {

  •    fpsText.setLocalTranslation(x, y, 0);
    
  • }

  • @Override
    public void setEnabled(boolean enabled) {
    super.setEnabled(enabled);
    @@ -233,6 +333,9 @@

    @Override
    public void update(float tpf) {

  •    if (locationChanged) {
    
  •        statsWindowLocation(statsScreenLocation);
    
  •    }
       if (showFps) {
           secondCounter += app.getTimer().getTimePerFrame();
           frameCounter ++;
    

[/java]

Why just not add to StatsAppState? I agree adding it there, but no need to add it on SimpleApplication, since some people use it and don’t use the StatsAppState NPE spoted).

@shirkit said: Why just not add to StatsAppState? I agree adding it there, but no need to add it on SimpleApplication, since some people use it and don't use the StatsAppState NPE spoted).

1000% agree with not adding any more methods to SimpleApplication. It has far too many methods and protected members as it is.

I’m not entirely sure why I added those methods in SimpleApplication really. Maybe I was thinking about ease of use. Who knows.

Oh and it seems I’m not resetting the “changed” flag in the code above. So I’ll fix that.

Anyway if there is no objection I’ll commit that tomorrow.

Hmmm… let me look around a little. In Mythruna I somehow already moved the stats without modifying JME.

Here is what I do in Mythruna:
[java]
StatsAppState statsState = stateManager.getState(StatsAppState.class);
BitmapText fps = statsState.getFpsText();
StatsView stats = statsState.getStatsView();
fps.setLocalTranslation(0, cam.getHeight(), 0);
stats.setLocalTranslation(0, cam.getHeight() - 165, 0);
[/java]

So it is possible already just not very convenient.

Rather than the constants, I’d almost prefer to have StatsAppState just report its current size. The user may have borders all around and want to push it in by some amount… and once the proper size available then just setting it where they want isn’t so difficult. Especially considering that “int screenCorner” should be an enum, etc… I’m not sure it’s worth all of the added complexity if StatsAppState just reports proper size. Something must know already since it draws a background now.

1 Like

Note: I’m still saying StatsAppState should have a set location… and I think it should be x,y,z.

1 Like

Nod to the z component. It would be nice to be able to situate it in the render order as well as relocate it.

Yeah, I think a setLocation() in the stats object itself makes sense. Not in SimpleApplication tho :slight_smile:

Before yesterday nobody gave a crap about this, now everyone has a modification to it. :wink:

I just want it out of my way. So, I’ll let you adults discuss on how best to implement this and I’ll modify the code above then repost the result. Finally I’ll commit when there’s an agreement.

Just tag me when you guys have reached a consensus.

@madjack said: Before yesterday nobody gave a crap about this, now everyone has a modification to it. ;)

Because it can already be done today without modifying the engine. Engine modifications need to be thought about and properly considered, though.

@madjack said: I just want it out of my way. So, I'll let you adults discuss on how best to implement this and I'll modify the code above then repost the result. Finally I'll commit when there's an agreement.

Just tag me when you guys have reached a consensus.

I don’t think there is any disagreement.
Do:
-let the stats tell us what size they are.
-have a 3D setLocation/getLocation on the stats (maybe give stats its own node inside the state)

Don’t:
-add anything to SimpleApplication
-add methods that take special magic numbers

Beyond that I think we were all just agreeing with each other in our critique of the original idea. :slight_smile:

Yeah, I wasn’t implying there was dissension, just things wanted by different people. :wink:

Methods have already been removed from SimpleApplication.

Since you don’t like the idea of using simple numbers properly documented in javadoc I could make a public enum of valid areas like: Stats.TopLeft, TopRight, BottomLeft, BottomRight and Arbitrary. A full vector3f for proper placement would be needed. Lacking that (null) it would be displayed at the default location.

As far as depth, I think I’ll use something like: setStatsLocation(Stats.TopLeft, new Vector3f(0, 0, -15)); (as mentioned above) Anything else than Arbitrary will get its X/Y ignored (would be mentioned in the Javadoc).

@madjack said: Yeah, I wasn't implying there was dissension, just things wanted by different people. ;)

Methods have already been removed from SimpleApplication.

Since you don’t like the idea of using simple numbers properly documented in javadoc I could make a public enum of valid areas like: Stats.TopLeft, TopRight, BottomLeft, BottomRight and Arbitrary. A full vector3f for proper placement would be needed. Lacking that (null) it would be displayed at the default location.

As far as depth, I think I’ll use something like: setStatsLocation(Stats.TopLeft, new Vector3f(0, 0, -15)); (as mentioned above) Anything else than Arbitrary will get its X/Y ignored (would be mentioned in the Javadoc).

I think if stats reports its size then you don’t even need the TopLeft thing and it’s overall more flexible. That was my point about that.

What size are you talking about? I don’t see the relation of size with its position on the screen.

@madjack said: What size are you talking about? I don't see the relation of size with its position on the screen.

How would you place it in the top right? You’d need to know its height and its width to properly locate the lower left corner of the stats.

So your design philosophy behind this is:
Let user retrieve the sizes and let them juggle with the final placement (by using reported sizes).

Mine is:
Give the user predefined ways to have that data displayed knowing it will work without having to juggle anything.

The end result is the same.

basically “topRight” becomes setLocation(camera.getWidth()-stats.getWidth(), camera.getHeight()-stats.getHeight(), z)

If you have setLocation, getWidth and getHeight then you don’t need anything else. An override of getLocation which takes (LEFT/TOP_LEFT/etc) as an enum and the enum knows how to calculate the location would be fine as an enhancement I guess.

@madjack said: So your design philosophy behind this is: Let user retrieve the sizes and let them juggle with the final placement (by using reported sizes).

Mine is:
Give the user predefined ways to have that data displayed knowing it will work without having to juggle anything.

The end result is the same.

Not the same: your way requires a more complicated API and limits the user in where they can place it. Or you keep adding constants "LeftCenter, TopCenter, RightCenter, CenterCenter, etc…)

…or you give the user enough info to put the stats wherever they want and locating it in the corners is still just one line of code.

FYI, you could use arbitrary and use whatever coordinates you felt like, but that’s beside the point. The goal was to give a simple solution where you provided the API where you wanted it without having to bother with anything else.

In this case I’ll implement a private solution. I’ve spent my allotted time on this.

@madjack said: FYI, you could use arbitrary and use whatever coordinates you felt like, but that's beside the point. The goal was to give a simple solution where you provided the API where you wanted it without having to bother with anything else.

In this case I’ll implement a private solution. I’ve spent my allotted time on this.

And the job of the core developers is to keep the API from bloating with one-off functions that replace a single line of code. You are extra-attached to it because it’s your one-off function but that doesn’t change the fact. If someone makes this call at all it’s likely to be once in their whole app and they will cut and paste it from their last app. That’s definitely not worth a new enum, etc. in my opinion.