how make javafx/8 dialog box shake more elegantly whenever user input wrong login name/password pair?.
since dialog in java8u40 not have one, set out make 1 myself. however, doesn't enough.
what's wrong it? can help? there better way in doing it?
public void logindialog() { // create custom dialog. dialog<pair<string, string>> dialog = new dialog<>(); dialog.settitle("mars simulation project"); dialog.setheadertext("log in"); dialog.setcontenttext("enter username , password : "); dialog.initmodality(modality.none); // set button types. buttontype loginbuttontype = new buttontype("login", buttondata.ok_done); dialog.getdialogpane().getbuttontypes().addall(loginbuttontype, buttontype.cancel); // create username , password labels , fields. gridpane grid = new gridpane(); grid.sethgap(10); grid.setvgap(10); grid.setpadding(new insets(20, 150, 10, 10)); textfield tfplayer = new textfield(); tfplayer.setprompttext("e.g. m03j"); passwordfield tfpassword = new passwordfield(); tfpassword.setprompttext("xxxx"); button defaultpwb = new button("use default"); button guestb = new button("as guest"); defaultpwb.setonaction(event -> { tfpassword.settext("msp0"); } ); guestb.setonaction(event -> { tfplayer.settext("guest_"); tfpassword.settext("msp0"); } ); grid.add(new label("player name :"), 0, 0); grid.add(tfplayer, 1, 0); grid.add(guestb, 2, 0); grid.add(new label("password :"), 0, 1); grid.add(tfpassword, 1, 1); grid.add(defaultpwb, 2, 1); // enable/disable login button depending on whether username entered. node loginbutton = dialog.getdialogpane().lookupbutton(loginbuttontype); loginbutton.setdisable(true); // validation (using java 8 lambda syntax). tfplayer.textproperty().addlistener((observable, oldvalue, newvalue) -> { loginbutton.setdisable(newvalue.trim().isempty()); } ); dialog.getdialogpane().setcontent(grid); // request focus on player name field default. platform.runlater(() -> tfplayer.requestfocus()); // convert result player name /host address pair when login // button clicked. dialog.setresultconverter(dialogbutton -> { if (dialogbutton == loginbuttontype) { return new pair<>(tfplayer.gettext(), tfpassword.gettext()); } return null; } ); optional<pair<string, string>> result = dialog.showandwait(); result.ifpresent(input -> { playername = tfplayer.gettext(); logger.info("player " + input.getkey() + " connecting server @ " + serveraddressstr); try { dialog.show(); makecontact(serveraddressstr); // obtain client id boolean issuccessful = sendregister(); if (issuccessful) { dialog.close(); // establish chat... } else { // shake dialog or send alert inform user // player name not valid dialogearthquakecenter dec = new dialogearthquakecenter(dialog); dec.starttimer(); try { system.out.println("start sleeping "); thread.sleep(2000); system.out.println("done sleeping "); } catch (interruptedexception e) {} logindialog(); } } catch (exception e) { e.printstacktrace(); } } );
so far, problem hit button "login", dialog close default.
therefore have use dialog.show() make show again.
[edit] this, however, still cannot prevent momentary gap happening (seeing dialog disappear , reappear).
after that, create instance of dialogearthquakecenter in order shake dialog.
note dialogearthquakecenter below direct modification of original :
import java.awt.event.actionevent; import java.awt.event.actionlistener; import javax.swing.timer; import javafx.animation.keyframe; import javafx.animation.timeline; import javafx.application.platform; import javafx.scene.control.dialog; import javafx.util.duration; import javafx.util.pair; public class dialogearthquakecenter { public static final int shake_distance = 10; public static final double shake_cycle = 50; public static final int shake_duration = 500; public static final int shake_update = 5; private dialog<pair<string, string>> dialog; private int x, y; private long starttime; private timer shaketimer; private final double two_pi = math.pi * 2.0; private timeline timeline; public dialogearthquakecenter(dialog<pair<string, string>> parent) { dialog = parent; } /** * creates , starts timer * * @return scene */ public void starttimer() { x = (int) dialog.getx(); y = (int) dialog.gety(); starttime = system.currenttimemillis(); // set earth time text update timeline = new timeline(new keyframe(duration.millis(shake_duration), ae -> startnudging())); //timeline.setcyclecount(javafx.animation.animation.indefinite); timeline.play(); } public void startnudging() { x = (int) dialog.getx(); y = (int) dialog.gety(); starttime = system.currenttimemillis(); shaketimer = new timer(shake_update, new actionlistener() { public void actionperformed(actionevent e) { shake(); } }); shaketimer.start(); } public void shake() { // calculate elapsed time long elapsed = system.currenttimemillis() - starttime; //system.out.println("elapsed " + elapsed); // use sin calculate x-offset double waveoffset = (elapsed % shake_cycle) / shake_cycle; double angle = waveoffset * two_pi; // offset x-location amount // proportional sine, shake_distance int shakenx = (int) ((math.sin(angle) * shake_distance) + x); platform.runlater(() -> { //dialog.hide(); dialog.setx(shakenx); //system.out.println("set shakenx " + shakenx); dialog.sety(y); dialog.show(); }); //try {thread.sleep(20);} //catch (interruptedexception ex) {} // should stop timer if (elapsed >= shake_duration) { stopshake(); } } public void stopshake() { shaketimer.stop(); platform.runlater(() -> { timeline.stop(); dialog.close(); }); } }
i did notice controlsfx dialog has shake() method.
does know if works ?
thanks comments!
there's way can add transition once user has click on login button using dialog
api, before window closed.
using dialog.show()
instead of dialog.showandwait()`, trick trapping click action on button, consume event, , perform required logic.
dialog.initmodality(modality.application_modal); dialog.show(); loginbutton.addeventfilter(eventtype.root, e->{ if(e.geteventtype().equals(actionevent.action)){ e.consume(); // (hardcoded) login validation boolean issuccessful = false; if (issuccessful) { dialog.close(); } else { // perform animation , close dialog (or other action) shaketransition anim = new shaketransition(dialog.getdialogpane(), t->dialog.close()); anim.playfromstart(); } } });
for shake animation, i've modified shaketransition
jasper potts, in order move dialog window, @jewelsea pointed out:
/** * animate shake effect on given node * * based on cachedtimelinetransition, transition uses timeline internally * , turns speed caching on animated node during animation. * * https://github.com/fxexperience/code/blob/master/fxexperiencecontrols/src/com/fxexperience/javafx/animation/cachedtimelinetransition.java * * , shaketransition * * https://github.com/fxexperience/code/blob/master/fxexperiencecontrols/src/com/fxexperience/javafx/animation/shaketransition.java * * @author jasper potts */ class shaketransition extends transition { private final interpolator web_ease = interpolator.spline(0.25, 0.1, 0.25, 1); private final timeline timeline; private final node node; private boolean oldcache = false; private cachehint oldcachehint = cachehint.default; private final boolean usecache=true; private final double xini; private final doubleproperty x = new simpledoubleproperty(); /** * create new shaketransition * * @param node node affect */ public shaketransition(final node node, eventhandler<actionevent> event) { this.node=node; statusproperty().addlistener((ov, t, newstatus) -> { switch(newstatus) { case running: starting(); break; default: stopping(); break; } }); this.timeline= new timeline( new keyframe(duration.millis(0), new keyvalue(x, 0, web_ease)), new keyframe(duration.millis(100), new keyvalue(x, -10, web_ease)), new keyframe(duration.millis(200), new keyvalue(x, 10, web_ease)), new keyframe(duration.millis(300), new keyvalue(x, -10, web_ease)), new keyframe(duration.millis(400), new keyvalue(x, 10, web_ease)), new keyframe(duration.millis(500), new keyvalue(x, -10, web_ease)), new keyframe(duration.millis(600), new keyvalue(x, 10, web_ease)), new keyframe(duration.millis(700), new keyvalue(x, -10, web_ease)), new keyframe(duration.millis(800), new keyvalue(x, 10, web_ease)), new keyframe(duration.millis(900), new keyvalue(x, -10, web_ease)), new keyframe(duration.millis(1000), new keyvalue(x, 0, web_ease)) ); xini=node.getscene().getwindow().getx(); x.addlistener((ob,n,n1)->(node.getscene().getwindow()).setx(xini+n1.doublevalue())); setcycleduration(duration.seconds(1)); setdelay(duration.seconds(0.2)); setonfinished(event); } /** * called when animation starting */ protected final void starting() { if (usecache) { oldcache = node.iscache(); oldcachehint = node.getcachehint(); node.setcache(true); node.setcachehint(cachehint.speed); } } /** * called when animation stopping */ protected final void stopping() { if (usecache) { node.setcache(oldcache); node.setcachehint(oldcachehint); } } @override protected void interpolate(double d) { timeline.playfrom(duration.seconds(d)); timeline.stop(); } }
and javafx application using login dialog:
@override public void start(stage primarystage) { button btn = new button(); btn.settext("show login dialog"); btn.setonaction(mevent -> { // create custom dialog. dialog<pair<string, string>> dialog = new dialog<>(); dialog.settitle("mars simulation project"); dialog.setheadertext("log in"); dialog.setcontenttext("enter username , password : "); dialog.initmodality(modality.none); // set button types. buttontype loginbuttontype = new buttontype("login", buttondata.ok_done); dialog.getdialogpane().getbuttontypes().addall(loginbuttontype, buttontype.cancel); // create username , password labels , fields. gridpane grid = new gridpane(); grid.sethgap(10); grid.setvgap(10); grid.setpadding(new insets(20, 150, 10, 10)); textfield tfplayer = new textfield(); tfplayer.setprompttext("e.g. m03j"); passwordfield tfpassword = new passwordfield(); tfpassword.setprompttext("xxxx"); button defaultpwb = new button("use default"); button guestb = new button("as guest"); defaultpwb.setonaction(event -> { tfpassword.settext("msp0"); } ); guestb.setonaction(event -> { tfplayer.settext("guest_"); tfpassword.settext("msp0"); } ); grid.add(new label("player name :"), 0, 0); grid.add(tfplayer, 1, 0); grid.add(guestb, 2, 0); grid.add(new label("password :"), 0, 1); grid.add(tfpassword, 1, 1); grid.add(defaultpwb, 2, 1); // enable/disable login button depending on whether username entered. node loginbutton = dialog.getdialogpane().lookupbutton(loginbuttontype); loginbutton.setdisable(true); // validation (using java 8 lambda syntax). tfplayer.textproperty().addlistener((observable, oldvalue, newvalue) -> { loginbutton.setdisable(newvalue.trim().isempty()); } ); dialog.getdialogpane().setcontent(grid); // request focus on player name field default. platform.runlater(() -> tfplayer.requestfocus()); dialog.initmodality(modality.application_modal); dialog.show(); loginbutton.addeventfilter(eventtype.root, e->{ if(e.geteventtype().equals(actionevent.action)){ e.consume(); // (hardcoded) login validation boolean issuccessful = false; if (issuccessful) { dialog.close(); } else { shaketransition anim = new shaketransition(dialog.getdialogpane(), t->dialog.close()); anim.playfromstart(); } } }); }); stackpane root = new stackpane(); root.getchildren().add(btn); scene scene = new scene(root, 300, 250); primarystage.settitle("shaky login dialog"); primarystage.setscene(scene); primarystage.show(); }