Strange libgdx TextField ui render error (IndexOutOfBoundsException) - user-interface

I get this error just once in a while when libgdx ui renders:
09-25 17:50:26.779 31171-32670/se.company.app.android E/AndroidRuntime﹕ FATAL EXCEPTION: GLThread 1126
Process: se.company.app.android, PID: 31171
java.lang.IndexOutOfBoundsException: index can't be >= size: 36 >= 36
at com.badlogic.gdx.utils.FloatArray.get(FloatArray.java:104)
at com.badlogic.gdx.scenes.scene2d.ui.TextField.calculateOffsets(TextField.java:214)
at com.badlogic.gdx.scenes.scene2d.ui.TextArea.calculateOffsets(TextArea.java:266)
at com.badlogic.gdx.scenes.scene2d.ui.TextField.draw(TextField.java:294)
at com.badlogic.gdx.scenes.scene2d.Group.drawChildren(Group.java:123)
at com.badlogic.gdx.scenes.scene2d.Group.draw(Group.java:57)
at com.badlogic.gdx.scenes.scene2d.ui.WidgetGroup.draw(WidgetGroup.java:154)
at com.badlogic.gdx.scenes.scene2d.ui.Table.draw(Table.java:117)
at com.badlogic.gdx.scenes.scene2d.Group.drawChildren(Group.java:110)
at com.badlogic.gdx.scenes.scene2d.Group.draw(Group.java:57)
at com.badlogic.gdx.scenes.scene2d.Stage.draw(Stage.java:128)
at se.company.app.screens.AbstractScreen.render(AbstractScreen.java:36)
at se.company.app.screens.InstructionsScreen.render(InstructionsScreen.java:80)
at com.badlogic.gdx.Game.render(Game.java:46)
at com.badlogic.gdx.backends.android.AndroidGraphics.onDrawFrame(AndroidGraphics.java:422)
at android.opengl.GLSurfaceView$GLThread.guardedRun(GLSurfaceView.java:1522)
at android.opengl.GLSurfaceView$GLThread.run(GLSurfaceView.java:1239)
09-25 17:50:30.823 31171-31171/se.company.app.android E/AndroidGraphics﹕ waiting for pause synchronization took too long; assuming deadlock and killing
09-25 17:50:30.833 31171-31171/se.company.app.android I/Process﹕ Sending signal. PID: 31171 SIG: 9
I can't figure out what it is about. Threading issue? But what? When I set text I do it with Gdx.app.postRunnable().
I have classes:
public class InstructionsScreen extends AbstractScreen {
private InputProcessor inputProcessor = new InputAdapter() {
#Override
public boolean keyDown(int keycode) {
if ((keycode == Input.Keys.ESCAPE) || (keycode == Input.Keys.BACK) ) {
game.setScreen(new BeginGameOptionsScreen(game, resources));
}
return false;
}
};
private Table table;
private ScrollPane resultsTextAreaScrollPane;
private TextArea resultsTextArea;
private TextArea textArea;
private float maxX,maxY,maxZ;
private Sound sound;
private long timer=0;
public InstructionsScreen(GameClass game, Resources resources) {
super(game, resources);
}
#Override
public void resize(int width, int height) {
addInputProcessor(inputProcessor);
table = new Table();
table.setFillParent(true);
stage.addActor(table);
TextArea resultsTextAreaBool;
resultsTextAreaBool = new TextArea("", resources.getSkin());
resultsTextAreaBool.setDisabled(true);
resultsTextAreaBool.setText(String.valueOf(Gdx.input.isPeripheralAvailable(Input.Peripheral.Accelerometer)));
table.add(resultsTextAreaBool).width(GraphicUtils.widthOfScreen(90)).height(GraphicUtils.heightOfScreen(20)).pad(GraphicUtils.onePercentHeight, GraphicUtils.onePercentWidth, GraphicUtils.onePercentHeight, GraphicUtils.onePercentWidth);
table.row();
resultsTextArea = new TextArea("", resources.getSkin());
resultsTextArea.setDisabled(true);
resultsTextArea.setText("");
table.add(resultsTextArea).width(GraphicUtils.widthOfScreen(90)).height(GraphicUtils.heightOfScreen(40)).pad(GraphicUtils.onePercentHeight, GraphicUtils.onePercentWidth, GraphicUtils.onePercentHeight, GraphicUtils.onePercentWidth);
table.row();
sound=resources.getRobotBlip();
}
#Override
public void render(float delta) {
super.render(delta);
float accelX = Gdx.input.getAccelerometerX();
float accelY = Gdx.input.getAccelerometerY();
float accelZ = Gdx.input.getAccelerometerZ();
if(accelX>maxX)
maxX=accelX;
if(accelY>maxY)
maxY=accelY;
if(accelZ>maxZ)
maxZ=accelZ;
if(Math.abs(maxX)>19f) {
timer++;
}
if(timer==1) {
sound.play();
}
if(timer>20) {
timer=0;
maxX=0;
}
Gdx.app.postRunnable(new Runnable() {
#Override
public void run() {
resultsTextArea.setText("x=" + String.valueOf(maxX) + "\ny=" + String.valueOf(maxY) + "\nz=" + String.valueOf(maxZ));
}
});
}
}
Problem seems to arise because of newlines "\n" in String above.
I use FreeTypeFontGenerator for font creation on the fly:
FreeTypeFontGenerator generator = new FreeTypeFontGenerator(Gdx.files.internal("data/fonts/anonymous-pro/Anonymous_Pro.ttf"));
FreeTypeFontGenerator.FreeTypeFontParameter freeTypeFontParameter = new FreeTypeFontGenerator.FreeTypeFontParameter();
freeTypeFontParameter.borderColor = Color.BLACK;
freeTypeFontParameter.borderWidth = 1;
freeTypeFontParameter.size = (int)(Gdx.graphics.getWidth()/25.0f);
anonymous_pro = generator.generateFont(freeTypeFontParameter);
generator.dispose();
Which I add with:
TextField.TextFieldStyle textFieldStyle=skin.get("default", TextField.TextFieldStyle.class);
textFieldStyle.font=anonymous_pro;
skin.add("default", textFieldStyle);
And retrieve with:
resultsTextArea = new TextArea("", resources.getSkin()); //Resources is my own class

I hope you'll excuse me for bumping this old issue, especially when there's already an answer. This page is one of very few references on Google to the subject matter and I found a different solution.
In short, I had to open up the .fnt file that I was using and remove the references to glyphs 0 and 10. I know you're using FreeTypeFontGenerator, but I suspect it's generating Glyph sizes for character codes 0 and 10, too. For the folks using Hiero or another generator, you can edit the .fnt file and change the following lines:
chars count=98
char id=0 x=0 y=0 width=0 height=0 xoffset=-1 yoffset=0 xadvance=9 page=0 chnl=0
char id=10 x=0 y=0 width=0 height=0 xoffset=-1 yoffset=0 xadvance=0 page=0 chnl=0
char id=32 x=0 y=0 width=0 height=0 xoffset=-1 yoffset=0 xadvance=9 page=0 chnl=0
To
chars count=96
char id=32 x=0 y=0 width=0 height=0 xoffset=-1 yoffset=0 xadvance=9 page=0 chnl=0
Note that id=0 and id=10 are gone and the count is updated.
This GitHub issue appears to address the root of the problem: https://github.com/libgdx/libgdx/issues/4570

Related

LibGDX / Box2d - only one rectangle is being rendered by debugrenderer

I'm revisiting LibGDX game programming and I am unfortunately having to re-learn stuff I used to know.
I'm currently using Tiled Map Editor to make a very simple Donkey Kong style level. I have around 20 rectangles in total for the level.
I've created a box2d world in my main GameScreen class and have a for loop to get the rectangle objects into the world and debugrenderer.
My problem is that only the bottom (and first) rectangle I drew is showing up. I have checked the scale, also I put a println() which tells me the object information has been parsed with all the rectangles info showing correct (ie. the rectangles x,y,w,h values) but as I say, only one rectangle shows up on the debugrenderer.
I've just got back into programming after around 6month break and so I'm hoping i've missed something simple. The same code in my old projects still works fine as I've tested some.
Here is my code, any help is massively appreciated. Thanks
public class GameScreen implements Screen {
SpriteBatch spriteBatch;
OrthographicCamera cam;
Viewport v;
TmxMapLoader mapLoader;
TiledMap map;
OrthogonalTiledMapRenderer mapRenderer;
World world;
Box2DDebugRenderer b2dr;
float mapScale = 10f/140f;
public GameScreen(){
spriteBatch = new SpriteBatch();
cam = new OrthographicCamera();
v = new FitViewport(Constants.V_WIDTH, Constants.V_HEIGHT, cam);
cam.setToOrtho(false, v.getWorldWidth(), v.getWorldHeight());
mapLoader = new TmxMapLoader();
map = mapLoader.load("level1.tmx");
mapRenderer = new OrthogonalTiledMapRenderer(map, mapScale);
world = new World(new Vector2(0,-9.8f), true);
b2dr = new Box2DDebugRenderer();
// box2d local variables
BodyDef bdef = new BodyDef();
PolygonShape shape = new PolygonShape();
FixtureDef fdef = new FixtureDef();
Body body;
// create platform object rectangles
for (MapObject object : map.getLayers().get(2).getObjects().getByType(RectangleMapObject.class)){
Rectangle rect = ((RectangleMapObject)object).getRectangle();
bdef.type = BodyDef.BodyType.StaticBody;
bdef.position.set(rect.getX() + rect.getWidth() / 2 * mapScale, rect.y + rect.getHeight() / 2 * mapScale);
body = world.createBody(bdef);
shape.setAsBox(rect.getWidth() / 2 * mapScale, rect.getHeight() / 2 * mapScale);
fdef.shape = shape;
body.createFixture(fdef);
}
}
#Override
public void show() {
}
#Override
public void render(float delta) {
update(delta);
clearScreen();
draw();
}
public void update(float dt){
mapRenderer.setView(cam);
}
public void clearScreen(){
Gdx.gl.glClearColor(1, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
}
public void draw(){
spriteBatch.setProjectionMatrix(cam.combined);
mapRenderer.render();
b2dr.render(world, cam.combined);
spriteBatch.begin();
spriteBatch.end();
}
#Override
public void resize(int width, int height) {
v.update(width, height);
}
#Override
public void pause() {
}
#Override
public void resume() {
}
#Override
public void hide() {
}
#Override
public void dispose() {
spriteBatch.dispose();
}
}
Sorry for wasting time here. I have fixed this.
It was just that I hadnt applied the scale of the map also to the x,y of the rectangles. So i change one line like so, and now it works:
bdef.position.set(rect.getX() * mapScale + rect.getWidth() / 2 * mapScale, rect.y * mapScale + rect.getHeight() / 2 * mapScale);

Keyboard covers TextField

As the center node of a gluon view I have a scrollpane which contains several textfields in a vbox. When one of these textfields becomes the focusowner and the keyboard shows up, the textfield doesn't get repositioned according to the layout of the keyboard, so it is left covered by the keyboard.
I tried putting
android:windowSoftInputMode="adjustResize"
in the AndroidManifest, but without any success.
As a workaround I translate the y-coordinates of the covered textfield to the visible area. When you press the android back button to hide the keyboard, the textfields position will be reset to its original state. The issue I'm getting here is that I don't get an event for the android back button, no matter where I add the listener:
view.addEventFilter(MobileEvent.BACK_BUTTON_PRESSED, evt -> eventFilter);
MobileApplication.getInstance().getGlassPane().addEventFilter(MobileEvent.BACK_BUTTON_PRESSED, evt -> eventFilter);
Is there any possibility to handle the positioning of a node under the keyboard, or to get a reference to the keyboard itself?
Only layers get the MobileEvent.BACK_BUTTON_PRESSED event. One solution is to go native and use the Android API.
This is the solution I could come up with so far:
public class PositionAdjuster {
public static void main(String[] args) { launch(args); }
private static final float SCALE = FXActivity.getInstance().getResources().getDisplayMetrics().density;
private Node nodeToAdjust;
private ObservableValue<Node> focusOwner;
private ViewGroup viewGroup;
private Rect currentBounds;
private DoubleProperty height;
private OnGlobalLayoutListener layoutListener;
public PositionAdjuster(Node nodeToAdjust, ObservableValue<Node> focusOwner) {
this.nodeToAdjust = nodeToAdjust;
this.focusOwner = focusOwner;
initLayoutListener();
}
private void initLayoutListener() {
double screenHeight = MobileApplication.getInstance().getScreenHeight();
height = new SimpleDoubleProperty(screenHeight);
height.addListener((ov, n, n1) -> onHeightChanged(n, n1));
layoutListener = () -> height.set(getCurrentHeigt());
viewGroup = FXActivity.getViewGroup();
viewGroup.getViewTreeObserver().addOnGlobalLayoutListener(layoutListener);
currentBounds = new Rect();
}
private float getCurrentHeigt() {
viewGroup.getRootView().getWindowVisibleDisplayFrame(currentBounds);
return currentBounds.height() / SCALE;
}
private void onHeightChanged(Number oldValue, Number newValue) {
double heightDelta = newValue.doubleValue() - oldValue.doubleValue();
if (heightDelta < 0) {
double maxY = getBoundsInScene(nodeToAdjust)).getMaxY();
double currentMaxY = heightDelta + maxY;
double result = currentMaxY- getBoundsInScene(focuseOwner.getValue()).getMaxY();
if (result < 0) {
nodeToAdjust.setTranslateY(result);
}
} else if (heightDelta > 0) {
nodeToAdjust.setTranslateY(0);
}
}
private Bounds getBoundsInScene(Node node) {
return node.localToScene(node.getBoundsInLocal());
}
public void removeListener() {
viewGroup.getViewTreeObserver().removeOnGlobalLayoutListener(layoutListener);
}
}
EDIT:
I think this is a more straightforward approach. The previous version was dependent on the maxY of noteToAdjust to be equal to the height of the screen, not taking into account e.g. the presence of a bottomBar. Now the maxY position of the focusedNode is validated against the visible screen height, and the difference is used to reposition its parent.
public AndroidPositionAdjuster(Node parent, ObservableValue<Node> focusOwner) {
this.parent = parent;
this.focusOwner = focusOwner;
initLayoutListener();
}
private void onHeightChanged(Number oldHeight, Number newHeight) {
double heightDelta = newHeight.doubleValue() - oldHeight.doubleValue();
if (heightDelta < 0) {
double maxY = newHeight.doubleValue();
double focusedNodeY = getBoundsInScene(focusOwner.getValue()).getMaxY();
double result = maxY - focusedNodeY;
if (result < 0) {
parent.setTranslateY(result);
}
} else if (heightDelta > 0) {
parent.setTranslateY(0);
}
}

My AsyncTask does not update UI smoothly (animation)

I want to make a TextView appear little by little, like animation. The problem is, the animation is not smooth. It gets stuck for a little while sometimes and then resumes. Sometimes even worse, it goes back... I mean, the TextView gets bigger and bigger but suddenly gets smaller then bigger again. Could anyone help me?
private class UnfoldTask extends AsyncTask<Integer, Integer, Integer> {
View view;
public UnfoldTask(View v) {
this.view = v;
ViewGroup.LayoutParams pa = view.getLayoutParams();
pa.height = 0;
view.setLayoutParams(pa);
}
#Override
protected Integer doInBackground(Integer... maxHeight) {
ViewGroup.LayoutParams pa = view.getLayoutParams();
while (pa.height < maxHeight[0]) {
pa.height += (int) (24 * getResources().getDisplayMetrics().density + 0.5f);
sleep(100);
publishProgress(pa.height);
}
return maxHeight[0];
}
private void sleep(int i) {
try {
Thread.sleep(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
#Override
protected void onProgressUpdate(Integer... values) {
ViewGroup.LayoutParams pa = view.getLayoutParams();
pa.height = values[0];
view.setLayoutParams(pa);
}
#Override
protected void onPostExecute(Integer result) {
ViewGroup.LayoutParams pa = view.getLayoutParams();
pa.height = result;
view.setLayoutParams(pa);
}
}
You should be using a scale animation for this. Here's an example:
ScaleAnimation animation = new ScaleAnimation(1, 2, 1, 2, centerX, centerY); // Scales from normal size (1) to double size (2). centerX/Y is the center of your text view. Change this to set the pivot point of your animation.
animation.setDuration(1000);
myTextView.startAnimation(animation);
You can use droidQuery to simplify this:
//this will set the height of myView to 0px.
$.with(myView).height(0);
//when you are ready to animate to height (in pixels):
$.with(myView).animate("{height:" + height + "}", new AnimationOptions());
Check the documentation if you want to get fancy - such as adding duration, and event callbacks. If you are still noticing non-smooth animation, consider adding the application attribute to your AndroidManifest:
android:hardwareAccelerated="true"

Handling orientation change during the asyntask in android

I'm currently having a problem in my asyntask. When the progress bar has start and once I rotate the screen, the progress bar disappear and the activity restart. I am trying to use
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
but What if my AsyncTask is not in my activity... Its in another class file common to many activity class. Then how can I get setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR); Thanks for anyone who will help.
this works in my app. I put this line in the manifest.
**android:configChanges="orientation|screenSize"**
Here is my solution to this problem:
#Override
protected void onPreExecute() {
Device.lockOrientation((Activity)context);
...
}
#Override
protected void onPostExecute(List<Hydrant> hydrants) {
Device.releaseOrientation((Activity)context);
...
}
The Device implementation:
public class Device {
public static void lockOrientation(Activity activity) {
Display display = ((WindowManager) activity.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
int rotation = display.getRotation();
int tempOrientation = activity.getResources().getConfiguration().orientation;
int orientation = 0;
switch(tempOrientation)
{
case Configuration.ORIENTATION_LANDSCAPE:
if(rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_90)
orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
else
orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
break;
case Configuration.ORIENTATION_PORTRAIT:
if(rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_270)
orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
else
orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
}
activity.setRequestedOrientation(orientation);
}
public static void releaseOrientation(Activity activity) {
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
}
}
As AsyncTasks are only meant to run for a few seconds, this is an acceptable solution to me as this rarely will affect a user. Especially with something as a ProgressDialog giving a nice indication of how long she will have to wait.

How to unmask a JavaFX PasswordField or properly mask a TextField?

In a UI of mine, I have a PasswordField like so (urm the one at the bottom!):
I want a user to be able to check the checkbox you see in the picture and have all "secret" password characters displayed. Not much different from the option we get from many modern password-asking UI:s floating around. However, I cannot find anything in the JavaFX API that let me do that?
If my worries hold true, then I would like to use a TextField that display the last key pressed for only half a second or until next key is pressed, and then he shall mask all previous user input. This would produce a cool animation effect that one can see sometimes in modern UI:s. However, is there a way for me to get hold of the OS dependent (I think it is OS dependent??) password echo character I should use?
If it is not possible to get that OS dependent character, then I'd be glad to use the character you see on the picture (JavaFX on a Windows 8 machine). What is the UTF-8 code point for this stranger?
> However, I cannot find anything in the JavaFX API that let me do that?
The PasswordField component does not display masked text by default. However you can use PasswordField with TextField and toggle masked/unmasked text using these components respectively. Where the unmasked text is shown by TextField, as in example demo below.
> I would like to use a TextField that display the last key pressed for only half a second or until next key is pressed, and then he shall mask all previous user input.
Since PasswordField, itself is a extended version of TextField. You can always build your own custom password textbox with properties you mentioned.
> is there a way for me to get hold of the OS dependent (I think it is OS dependent??) password echo character I should use?
Frankly did not grab what you are saying here. You can track text changes by adding change listener to PasswordField.textPrperty() and do animations, timers etc. You can override the default bullet mask by extending PasswordFieldSkin and using it through CSS -fx-skin. See the definition of bullet in its source here:
public class PasswordFieldSkin extends TextFieldSkin {
public static final char BULLET = '\u2022';
public PasswordFieldSkin(PasswordField passwordField) {
super(passwordField, new PasswordFieldBehavior(passwordField));
}
#Override protected String maskText(String txt) {
TextField textField = getSkinnable();
int n = textField.getLength();
StringBuilder passwordBuilder = new StringBuilder(n);
for (int i=0; i<n; i++) {
passwordBuilder.append(BULLET);
}
return passwordBuilder.toString();
}
}
Finally, Here is kick off demo app of showing password characters using bindings:
#Override
public void start(Stage primaryStage) {
// text field to show password as unmasked
final TextField textField = new TextField();
// Set initial state
textField.setManaged(false);
textField.setVisible(false);
// Actual password field
final PasswordField passwordField = new PasswordField();
CheckBox checkBox = new CheckBox("Show/Hide password");
// Bind properties. Toggle textField and passwordField
// visibility and managability properties mutually when checkbox's state is changed.
// Because we want to display only one component (textField or passwordField)
// on the scene at a time.
textField.managedProperty().bind(checkBox.selectedProperty());
textField.visibleProperty().bind(checkBox.selectedProperty());
passwordField.managedProperty().bind(checkBox.selectedProperty().not());
passwordField.visibleProperty().bind(checkBox.selectedProperty().not());
// Bind the textField and passwordField text values bidirectionally.
textField.textProperty().bindBidirectional(passwordField.textProperty());
VBox root = new VBox(10);
root.getChildren().addAll(passwordField, textField, checkBox);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Demo");
primaryStage.setScene(scene);
primaryStage.show();
}
You need create three elements:
TextField : the password visible field
PasswodField : the password not visible field
CheckBox : the toggle visibility field
You place the passwords fields in the same position(x, y):
<PasswordField fx:id="pass_hidden" layoutX="X" layoutY="Y" />
<TextField fx:id="pass_text" layoutX="X" layoutY="Y"/>
<CheckBox fx:id="pass_toggle" onAction="#togglevisiblePassword" .... />
Note: Replaces the value of X and Y.
Add in your controller:
#FXML
private TextField pass_text;
#FXML
private CheckBox pass_toggle;
#FXML
private Button btn_start_stop;
/**
* Controls the visibility of the Password field
* #param event
*/
#FXML
public void togglevisiblePassword(ActionEvent event) {
if (pass_toggle.isSelected()) {
pass_text.setText(pass_hidden.getText());
pass_text.setVisible(true);
pass_hidden.setVisible(false);
return;
}
pass_hidden.setText(pass_text.getText());
pass_hidden.setVisible(true);
pass_text.setVisible(false);
}
//Run
#Override
public void initialize(URL location, ResourceBundle resources) {
this.togglevisiblePassword(null);
}
If you want to know the value of the password you can create a method that returns it:
private String passwordValue() {
return pass_toggle.isSelected()?
pass_text.getText(): pass_hidden.getText();
}
I know this is older, but i was searching for answer and this is my solution:
#FXML
private JFXButton showpassword;
private String password;
showpassword.addEventFilter(MouseEvent.MOUSE_PRESSED, e -> {
password = passwordField.getText();
passwordField.clear();
passwordField.setPromptText(password);
});
showpassword.addEventFilter(MouseEvent.MOUSE_RELEASED, e -> {
passwordField.setText(password);
passwordField.setPromptText("Password");
});
Using button with graphic like "WIN10 Eye - unmask password"
You could use a custom Tooltip to show the password, and use the Checkbox to show / hide the Tooltip.
The code for this demo can be found here.
void viewpass(ActionEvent event) {
if (checkpass.isSelected()){
pass.setPromptText(pass.getText());
pass.setText("");
pass.setDisable(true);
}else {
pass .setText(pass.getPromptText());
pass.setPromptText("");
pass.setDisable(false);
}
}
You can also do it using textfield and password field with radio button As follows.
import javafx.fxml.Initializable;
import com.jfoenix.controls.*;
import com.jfoenix.controls.JFXPasswordField;
import com.jfoenix.controls.JFXRadioButton;
import javafx.fxml.FXML;
import java.net.URL;
import java.util.ResourceBundle;
public class Controller implements Initializable{
#FXML
private JFXPasswordField PasswordField;
#FXML
private JFXRadioButton passVisible;
#FXML
private JFXTextField textField1;
#Override
public void initialize(URL location, ResourceBundle resources)
{
textField1.textProperty().bind(PasswordField.textProperty());
textField1.visibleProperty().bind(passVisible.selectedProperty());
PasswordField.visibleProperty().bind(passVisible.selectedProperty().not());
}
}
well, the password field has one property that can be set the text in bullets.. this method maskText(String txt) stays on skin.. you can replace this with a new Skin.. when you type the method maskText test if you can raplace in bullets.. use one boolean to inform.. you can reuse this code from another event. it's an example. Regards
public class Main extends Application {
#Override
public void start(Stage stage) throws Exception {
StackPane root = new StackPane();
root.setAlignment(Pos.CENTER);
root.setPadding(new Insets(50));
PasswordField passwordField = new PasswordField();
passwordField.setSkin(new VisiblePasswordFieldSkin(passwordField));
root.getChildren().add(passwordField);
stage.setScene(new Scene(root, 400, 400));
stage.show();
}
}
class VisiblePasswordFieldSkin extends TextFieldSkin {
private final Button actionButton = new Button("View");
private final SVGPath actionIcon = new SVGPath();
private boolean mask = true;
public VisiblePasswordFieldSkin(PasswordField textField) {
super(textField);
actionButton.setId("actionButton");
actionButton.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
actionButton.setPrefSize(30,30);
actionButton.setFocusTraversable(false);
actionButton.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, new Insets(0))));
getChildren().add(actionButton);
actionButton.setCursor(Cursor.HAND);
actionButton.toFront();
actionIcon.setContent(Icons.VIEWER.getContent());
actionButton.setGraphic(actionIcon);
actionButton.setVisible(false);
actionButton.setOnMouseClicked(event -> {
if(mask) {
actionIcon.setContent(Icons.VIEWER_OFF.getContent());
mask = false;
} else {
actionIcon.setContent(Icons.VIEWER.getContent());
mask = true;
}
textField.setText(textField.getText());
textField.end();
});
textField.textProperty().addListener((observable, oldValue, newValue) -> actionButton.setVisible(!newValue.isEmpty()));
}
#Override
protected void layoutChildren(double x, double y, double w, double h) {
super.layoutChildren(x, y, w, h);
layoutInArea(actionButton, x, y, w, h,0, HPos.RIGHT, VPos.CENTER);
}
#Override
protected String maskText(String txt) {
if (getSkinnable() instanceof PasswordField && mask) {
int n = txt.length();
StringBuilder passwordBuilder = new StringBuilder(n);
for (int i = 0; i < n; i++) {
passwordBuilder.append(BULLET);
}
return passwordBuilder.toString();
} else {
return txt;
}
}
}
enum Icons {
VIEWER_OFF("M12 6c3.79 0 7.17 2.13 8.82 5.5-.59 1.22-1.42 2.27-2." +
"41 3.12l1.41 1.41c1.39-1.23 2.49-2.77 3.18-4.53C21.27 7.11 17 4 12 4c-1.27 " +
"0-2.49.2-3.64.57l1.65 1.65C10.66 6.09 11.32 6 12 6zm-1.07 1.14L13 9.21c.57.25 1.03.71 " +
"1.28 1.28l2.07 2.07c.08-.34.14-.7.14-1.07C16.5 9.01 14.48 7 12 7c-.37 0-.72.05-1.07." +
"14zM2.01 3.87l2.68 2.68C3.06 7.83 1.77 9.53 1 11.5 2.73 15.89 7 19 12 19c1.52 0 2.98-.29 " +
"4.32-.82l3.42 3.42 1.41-1.41L3.42 2.45 2.01 3.87zm7.5 7.5l2.61 2.61c-.04.01-.08.02-.12.02-1.38 " +
"0-2.5-1.12-2.5-2.5 0-.05.01-.08.01-.13zm-3.4-3.4l1.75 1.75c-.23.55-.36 1.15-.36 1.78 0 2.48 2.02 " +
"4.5 4.5 4.5.63 0 1.23-.13 1.77-.36l.98.98c-.88.24-1.8.38-2.75.38-3.79 0-7.17-2.13-8.82-5.5.7-1.43 1.72-2.61 2.93-3.53z"),
VIEWER("M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7." +
"5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z");
private String content;
Icons(String content) {
this.content = content;
}
public String getContent() {
return content;
}
}
View in GitHub

Resources