What I'm trying to do is add a label to follow a slider knob and display the knob's value. I currently have a vertical slider and a label stacked inside a table cell. I'm able to position the label to where the knob is and update it's text correctly but am having a problem while moving the knob.
When I move the knob I can make the label follow it as long as I don't update the text. But if I update the text then the label re-centers itself in the cell till the knob has stopped moving at which point it places itself on the knob.
How do I make it so that the label doesn't reposition while it's updating text?
Here's some sample code of what I have going on:
public class OptionsWindow implements Screen {
private Stage stage;
private Table table;
private Slider slider;
private Label label;
private Skin textSkin = new Skin(Gdx.files.internal("skins/skin.json"),
new TextureAtlas(Gdx.files.internal("skins/text.pack")));
private Skin sliderSkin = new Skin(Gdx.files.internal("skins/skin.json"),
new TextureAtlas(Gdx.files.internal("skins/text.pack")));
private min = 1, max = 2;
#Override
public void show() {
stage = new Stage();
table = new Table();
slider = new Slider(min, max, 0.001f, true, sliderSkin);
label = new Label(""), textSkin);
table.stack(label, slider).height(1000);
table.setFillParent(true);
stage.addActor(table);
Gdx.input.setInputProcessor(stage);
}
#Override
public void render() {
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
label.setY(slider.getValue() - min);
label.setText(slider.getValue());
stage.act();
stage.draw();
}
}
I've done a lot of searching for an answer to this question but have yielded no results. I have also tried placing the label in a container and moving the container while changing the label text, but the same result happens. I'm sure somebody had done this before. It seems like something people would want. Please, any help is welcome.
Never done it before, I always show the amount in a label next to or above the slider to give feedback. But the result you are after gives a nice touch. Did you try adding a ChangeListener to your slider? Everytime the slider changes this would be called. So if you insert the new position and text in there it should be updating correctly.
//Slider has to be "final" for the listener.
final Slider slider = new Slider(0, 10, 1, true, skin);
slider.addListener(new ChangeListener() {
#Override
public void changed(ChangeEvent event, Actor actor) {
label.setY(slider.getValue() - min);
label.setText(slider.getValue());
}
});
This does not need to be in your render method. It's a listener so whenever something happens the listener listens for it will execute.
Ok, so I came up with a work around. It would seem that labels move back to their cell alignment position while updating text. to solve this I simply placed my label in a table with a row on either side like so:
Table subTable = new Table();
subTable.add().row();
subTable.add(label).row();
subTable.add();
Then in render I calculate new height values for the exterior cells and apply them:
float topH, botH;
// Calculate heights
topH = (1 - slider.getPercent()) * parentTable.getHeight();
botH = slider.getPercent() * parentTable.getHeight();
// Apply heights to exterior table cells
subTable.getCells().get(0).height(topH);
subTable.getCells().get(2).height(botH);
// Update label text
label.setText(slider.getValue());
This is just a simple form of what I have. You'll want to take into account the label height to get it to position correctly. Also if you want it to follow with the slider at the slider's set animateDuration, you'll want to use getVisualPercent() instead of getPercent(). However this causes issues when your finger flies out of the cell. For some reason the visual percent is linked to where the slider knob is when your finger leaves the cell, not where it ends up.
My work around for this was to just set the animateDuration to 0.0f and just use getPercent(). If I or someone comes up with a solution to the getVisualPercent() option, then that would be nice to have posted as well.
Related
In libgdx I have a ScrollPane in my application and I can't figure out how to get it to scroll without extra space at the top. Currently, there is a massive space at the top of the pane, and when I scroll down, the text (high scores) slowly fill up the space.
BEFORE SCROLLING:
AFTER SCROLLING:
Is it possible to start without the space (so the pane is completely filled) and when I scroll, I can scroll through the high scores without them moving upwards?
I noticed I can accomplish this by removing the following line.
table.setTransform(true);
But if I do this, I cannot scale or set origin on the table so the text appears too large.
Below is my scrollpane creation method:
private void createScrollPane(ArrayList<String> scrollPaneScores) {
scrollPaneViewport = new FitViewport(800, 480);
stage = new Stage(scrollPaneViewport);
Skin skin = new Skin(Gdx.files.internal("skin/uiskin.json"));
Table scrollableTable = new Table();
scrollableTable.setFillParent(true);
stage.addActor(scrollableTable);
Table table = new Table();
for (String score : scrollPaneScores) {
table.add(new TextButton(score, skin)).row();
}
table.pack();
table.setTransform(true);
//table.setTransform(true); //clipping enabled ... this setting makes scrolling not occur in uniform fashion
table.setOrigin(400, 0);
table.setScale(0.75f);
final ScrollPane scroll = new ScrollPane(table, skin);
//scrollableTable.add(scroll).expand().fill();
scrollableTable.add(scroll).maxHeight(300);
//stage.setDebugAll(true);
//When stage is created, set input processor to be a multiplexer to look at both screen and stage controls
inputMultiplexer.addProcessor(inputProcessorScreen);
inputMultiplexer.addProcessor(stage);
Gdx.input.setInputProcessor(inputMultiplexer);
scrollPaneCreated = true;
}
Remove the setFillParent line. It is an old problem with tables inside scrollpanes.
Try removing the scrollableTable.setFillParent(true);
There are obviously the two methods getWidth and getHeight but they return the old label size if we just changed the Label text value.
For example, in this code, the background is not correctly resized:
Label speedLabel = new Label();
Rectangle backgroundLabel = new Rectangle();
// Some initialization
// Change the label text
speedLabel.setText(connection.getSpeed_bps()); // Sets a bigger number
// Adjust the background
backgroundLabel.setWidth(speedLabel.getWidth());
backgroundLabel.setHeight(speedLabel.getHeight());
After the initialization, my Label is like that:
And after the text change and the background adjustment, my Label is like that:
I had a look at this post but it recommends a deprecated method:
How to get label.getWidth() in javafx
The reason for returning the "old" size that the label actually doesn't get updated on the GUI in the moment when you set the textProperty of it, therefore the size is not changed.
You can listen to the widthProperty and heightProperty of the Label and resize the Rectangle in the listener:
speedLabel.widthProperty().addListener((obs, oldVal, newVal) -> {
backgroundLabel.setWidth(newVal.doubleValue());
});
speedLabel.heightProperty().addListener((obs, oldVal, newVal) -> {
backgroundLabel.setHeight(newVal.doubleValue());
});
or simply use bindings between the properties:
backgroundLabel.heightProperty().bind(speedLabel.heightProperty());
backgroundLabel.widthProperty().bind(speedLabel.widthProperty());
But if you just want to achieve a Label with some background, you don't actually need a Rectangle, just some CSS - you can check this question: FXML StackPane doesn't align children correctly
You can do it in two ways.
Approach 1:
Give the background color of your label as that of rectangle. So that whatever is the Label's size, your background rectangle will take the width accordingly.
label.setStyle("-fx-background-color:grey; -fx-padding:5");
Approach 2:
Bind the Rectangle's size according to your label size
rectangle.prefWidthProperty(label.widthProperty());
rectangle.prefHeightProperty(label.heightProperty());
I've been working on some app's using libgdx & I can't figure out how to position a button via coords.
public void create() {
batch = game.getSpriteBatch();
background = new Texture("data/textures/menuScreen_small.png");
bubbleArray = new Array<Bubble>();
Gdx.input.setInputProcessor(stage);
for (int i = 0; i < 20; i++) {
bubbleArray.add(new Bubble(MathUtils.random(0,
Gdx.graphics.getWidth()), MathUtils.random(0,
Gdx.graphics.getHeight())));
}
table = new Table();
stage.addActor(table);
table.setSize(1280, 720);
table.setPosition(0, 0);
//table.left().top();
// table.debug();
//table.row().minHeight(55).maxHeight(55);
skin = new Skin(Gdx.files.internal("data/ui/uiskin.json"));
/*
* HomeScreen [Button 1]
*/
final TextButton camButton = new TextButton("Camera", skin);
//Add button
table.add(camButton);//.left().expandX().fillX();
camButton.setPosition(0, 0);
//Button Height
//table.row().minHeight(55).maxHeight(55);
//Button Width
//table.row().minWidth(55).maxWidth(55);
In the code above I am trying to position my textButton "camButton" at 0,0 on the screen.
when I start the program it's in the very center of the screen.
If anyone has some guides or tips let me know, I need to learn this stuff.
a small 'fix'
Not exactly a fix; but by removing the table in general solved this problem. By doing this I'm adding it to the stage instead of stage > table > button. Any comments to that?
If you just want to be able to fix buttons to absolute positions like 0,0 there is no need to add the items to a table, just add you button to the stage
TextButton button = new TextButton("Floating Button", skin);
button.setPosition(10, 10);
stage.addActor(button);
Edit
If you still want the controlled positioning that the table provides check out the guide for the table layout it has many good examples Table Layout
If you want to position the table
table.left().bottom();
To position items inside a table
table.add(button).expand().bottom().right();
I had your problem. My requirement for the whole screen or part of the screen was. I solved my problem using WidgetGroup layout.
actor.setPosition(100,200);
yourWidgetGroup.addActor(actor);
I just can't figure this, and I am pulling my hair out right now!!
I have a Stage with a Label added to it, I set everything up and the first time I call Stage.draw() everything works fine. However, as soon as I set the text of the Label nothing gets drawn. Funny thing is, when I don't change the text it draws perfectly as expected, but when I call label.setText("THE TEXT") it just doesn't draw.
I have stepped through my code and I have noted down the height, width, x and y values before and after setting text of the Label, and they are all the same (before and after).
Also, when I draw the Stage it's drawn above a Sprite, and positioning the Sprite is based on the Label's position.
The Sprite draws fine before I set text on the Label and after.
PS: I have also made sure that the Sprite is not drawn "over" the Label.
This is my setup:
I have a MainGame class that renders a Player class, when ever the back button is pressed the Sprite with the Stage gets drawn, or should get drawn.
spriteBatch.begin();
player.update(spriteBatch, delta);
spriteBatch.end();
// the pause menu is drawn with a separate sprite batch as it needs to be in middle relative to the screen and above everything else
if (player.isPaused()){
messageSpriteBatch.begin();
messageSprite.draw(messageSpriteBatch);
messageSpriteBatch.end();
messageStage.draw(); // the stage doesn't seem to be getting drawn
}
Player class - update method
if (!paused){
// removed for clarity
}else{
// on MainGame class we render a small box with one of the following messages
// READY
// PAUSE
// QUIT?
// GAME OVER
if (Gdx.input.justTouched()){
paused = false;
}
spriteBatch.setProjectionMatrix(camera.combined);
camera.update();
}
messageSpriteBatch = new SpriteBatch();
messageStage = new Stage(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
messageFont = new BitmapFont(Gdx.files.internal("fonts/fontfile.fnt"));
messageStyle = new LabelStyle();
messageStyle.font = messageFont;
messageLabel = new Label("READY", messageStyle);
This is how I initialise my Label, Sprite and Stage:
float fontScaleX = Gdx.graphics.getWidth()/SCALE_X_DIVIDER;
float fontScaleY = Gdx.graphics.getHeight()/SCALE_Y_DIVIDER;
messageLabel.setFontScale(fontScaleX*1.4f, fontScaleY*4.2f);
messageLabel.setPosition((messageStage.getWidth()/2)-((messageLabel.getWidth()/2)*messageLabel.getFontScaleX()), (messageStage.getHeight()/2)-((messageLabel.getHeight()/2)*messageLabel.getFontScaleY())+(player.getScoreboard().getSize().y/2));
messageStage.addActor(messageLabel);
messageStage.act();
messageTexture = new Texture(Gdx.files.internal("images/message_background.png"));
messageSprite = new Sprite(messageTexture);
messageSprite.setSize((messageLabel.getWidth()*messageLabel.getFontScaleX())*1.5f, (messageLabel.getHeight()*messageLabel.getFontScaleY())*3);
messageSprite.setPosition(messageLabel.getX()-(messageSprite.getWidth()/6), messageLabel.getY()-messageSprite.getHeight()/2);
Please help me, before I get bald xD
Well the width and height of the label is usually set once when creating the Label, matching the width and height of the initial text passed to the Constructor. You of course can make it bigger by setting the width and height later on. Then you could also use alignments by the way...
Are you using any special characters when changing the text, so the font has a problem with that?
Maybe posting some code could give more insights...
Haha, well, I found out what was up, it was my font file, you see when I was editing it, I used the text READY, so when I saved it, I didn't know that the text written is what is saved, so when I looked at the font.png file, I found that it only contained the letters ADERY.
Thanks for the help anyway =]
I created a slider inside of a table like shown in the following code example as I know that the minimum width of the background is used for the slider width:
public OptionScreen(MainClass game) {
super(game);
preference = new PreferencesHelper();
font = this.getDefaultFont(25);
this.table = new Table();
if (Config.DEBUG)
this.table.debug();
// add volumenlabel
LabelStyle style = new LabelStyle(font, Color.WHITE);
volumenLabel = new Label(Config.VOLUMEN_LABLE, style);
table.add(volumenLabel).colspan(2);
table.row();
// add slider
Skin skin = new Skin();
skin.add("sliderbackground",
this.game.manager.get("data/sliderbackground.png"));
skin.add("sliderknob", this.game.manager.get("data/sliderknob.png"));
SliderStyle sliderStyle = new SliderStyle();
sliderStyle.background = skin.getDrawable("sliderbackground");
sliderStyle.background.setMinWidth(600f);
sliderStyle.knob = skin.getDrawable("sliderknob");
volumenSlider = new Slider(0f, 1f, 0.1f, false, sliderStyle);
volumenSlider.setValue(preference.getVolumen()); // load current volumen
volumenSlider.addListener(new ChangeListener() {
#Override
public void changed(ChangeEvent event, Actor actor) {
volumeValue.setText(String.format("%.01f",
volumenSlider.getValue()));
// sett the preference
preference.setVolumen(volumenSlider.getValue());
}
});
// add volslider to stage
table.add(volumenSlider);
volumenLabel.invalidate();
// table
style = new LabelStyle(font, Color.WHITE);
// set current volumen
volumeValue = new Label(
String.format("%.01f", volumenSlider.getValue()), style);
volumenLabel.setAlignment(2);
table.add(volumeValue).width(50f);
table.row();
initBackButton();
// init table
table.setPosition(Config.VIRTUAL_VIEW_WIDTH / 2,
Config.VIRTUAL_VIEW_HEIGHT / 2 - Config.BLOCK_SIZE * 10);
// add a nice fadeIn to the whole table :)
table.setColor(0, 0, 0, 0f);
table.addAction(Actions.fadeIn(2f)); // alpha fade
table.addAction(Actions.moveTo(Config.VIRTUAL_VIEW_WIDTH / 2,
Config.VIRTUAL_VIEW_HEIGHT / 2, 2f)); // move to center of the
// screen
// add to stage
this.stage.addActor(table);
}
The slide is inside a table with no width set. I already took a look if the width is set and if the calculation for the prefWidth of the slider does uses the set 600f.
Math.max(style.knob == null ? 0 : style.knob.getMinWidth(), style.background.getMinWidth())
Is the calculation for the width of the slider inside the Sliderclass. If I calculate that and log it, it loggs the desired 600f.
Everything seems right to me but the slider is rendered way to small for the 600 I set.
The background and knobtextures are 24x24.
So I hope you guys can tell me what I am doing wrong.
The folution is, that it's inside an table so the width is defined by the table width attibut for the spec. col and row.
So the fix is pretty short:
table.add(volumenSlider).width(600).height(60);
And its 600width and 60 height.
The wiki got edited to be more clear about this:
UI widgets do not set their own size and position. Instead, the parent widget sets the size and position of each child. Widgets provide a minimum, preferred, and maximum size that the parent can use as hints. Some parent widgets, such as Table, can be given constraints on how to size and position the children. To give a widget a specific size in a layout, the widget's minimum, preferred, and maximum size are left alone and size constraints are set in the parent.
Layout at Wiki
speedSlider.getStyle().knob.setMinHeight(100)