Libgdx Table widget's row spacing is inconsistent with certain viewports

Hello, everyone!

Finally I understood that making my own (even simple) UI library is bad idea.

But I have nasty problem with Scene2d Table widget.

When I use ScreenViewport or ScalingViewport with a Stage, everything’s fine:

https://thumb.ibb.co/isrmSw/cool.png

When I use FitViewport, FillViewport, StretchViewport or ExtendViewport with a Stage, this happens:

https://thumb.ibb.co/g330nw/ugly.png

So… what the hell is going on?

Font setup:


final float scale = WORLD_HEIGHT / Gdx.graphics.getHeight();

...

AssetHelper.loadFont(THEBOLDFONT, 6f, 0.8f, 0, scale, assetManager);

...

public static void loadFont(String fileName,
                            float size,
                            float spaceX,
                            float spaceY,
                            float scale,
                            AssetManager assetManager) {
    FreetypeFontLoader.FreeTypeFontLoaderParameter fontLoaderParameter = new FreetypeFontLoader.FreeTypeFontLoaderParameter();
    fontLoaderParameter.fontFileName = fileName;
    fontLoaderParameter.fontParameters.magFilter = Texture.TextureFilter.Linear;
    fontLoaderParameter.fontParameters.minFilter = Texture.TextureFilter.Linear;
    fontLoaderParameter.fontParameters.size = round(size / scale);
    fontLoaderParameter.fontParameters.spaceX = round(spaceX / scale);
    fontLoaderParameter.fontParameters.spaceY = round(spaceY / scale);
    assetManager.load(fileName, BitmapFont.class, fontLoaderParameter);
    assetManager.finishLoading();
    BitmapFont font = assetManager.get(fileName, BitmapFont.class);
    font.setUseIntegerPositions(false);

    // this line was enabled for FitViewport, FillViewport, StretchViewport and ExtendViewport
    // otherwise font is not scaled (too big)
    font.getData().setScale(scale);
}

Menu screen with Stage setup:


package com.mmx256.vectroid.screen;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import com.badlogic.gdx.scenes.scene2d.ui.Table;
import com.badlogic.gdx.scenes.scene2d.ui.TextButton;
import com.badlogic.gdx.utils.Align;
import com.badlogic.gdx.utils.viewport.FitViewport;
import com.badlogic.gdx.utils.viewport.ScreenViewport;

import static br.com.objectos.core.lang.Preconditions.checkNotNull;

public class Ui_MainMenuScreen extends InputScreenAdapter {

    private int lastWidth;
    private int lastHeight;

    private AssetManager       assetManager;
    private SpriteBatch        batch;
    private OrthographicCamera camera;
    private FitViewport        viewport;

    private Stage stage;
    private Skin  skin;

    public Ui_MainMenuScreen(float worldWidth,
                             float worldHeight,
                             AssetManager assetManager,
                             Skin skin) {
        checkNotNull(assetManager, "assetManager cannot be null.");

        this.assetManager = assetManager;
        this.skin = skin;

        batch = new SpriteBatch();
        camera = new OrthographicCamera();
        viewport = new FitViewport(worldWidth, worldHeight, camera);
        viewport.apply(true);

        batch.setTransformMatrix(camera.view);
        batch.setProjectionMatrix(camera.projection);

        stage = new Stage(viewport);

        final TextButton newGameBtn      = new TextButton("NEW GAME", this.skin, "default");
        final TextButton continueGameBtn = new TextButton("CONTINUE GAME", this.skin, "default");
        final TextButton switchModeBtn   = new TextButton("SWITCH MODE", this.skin, "default");
        final TextButton settingsBtn     = new TextButton("SETTINGS", this.skin, "default");
        final TextButton scoreboardBtn   = new TextButton("SCORE BOARD", this.skin, "default");
        final TextButton exitBtn         = new TextButton("EXIT", this.skin, "default");

        System.out.println(newGameBtn.getHeight());
        System.out.println(continueGameBtn.getHeight());
        System.out.println(switchModeBtn.getHeight());
        System.out.println(settingsBtn.getHeight());
        System.out.println(scoreboardBtn.getHeight());
        System.out.println(exitBtn.getHeight());

        final Table mainTable = new Table();
        mainTable.setWidth(stage.getWidth());
        mainTable.setHeight(stage.getHeight());
        mainTable.align(Align.center);

        mainTable.add(newGameBtn);
        mainTable.row();
        mainTable.add(continueGameBtn);
        mainTable.row();
        mainTable.add(switchModeBtn);
        mainTable.row();
        mainTable.add(settingsBtn);
        mainTable.row();
        mainTable.add(scoreboardBtn);
        mainTable.row();
        mainTable.add(exitBtn);

        stage.addActor(mainTable);
    }

    @Override
    public void render(float delta) {
        camera.update();
        Gdx.gl.glClearColor(0, 0, 0, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
        stage.act(delta);
        stage.draw();
    }

    @Override
    public void resize(int width, int height) {
        if (width != lastWidth || height != lastHeight) {
            stage.getViewport().update(width, height, true);
            //            viewport.update(width, height, true);
            lastWidth = width;
            lastHeight = height;
        }
    }

    @Override
    public void dispose() {
        super.dispose();
        batch.dispose();
        stage.dispose();
    }
}

Am I doing something wrong being unaware of it?