PlayN canvas.drawText() weird behaviour in HTML (Java is ok) - playn

I want to use the canvas.drawText() method to draw some text frame after frame. When I run my game in Java, everything is fine. But when I run in html, the behaviour is different. I isolated the code (see below) and I get the same result.
package textbug.core;
import static playn.core.PlayN.*;
import playn.core.Canvas;
import playn.core.CanvasImage;
import playn.core.Game;
import playn.core.Image;
import playn.core.ImageLayer;
public class TextBugIsolated implements Game {
Canvas canvas;
int x = 20;
#Override
public void init() {
// create and add background image layer
Image bgImage = assetManager().getImage("images/bg.png");
ImageLayer bgLayer = graphics().createImageLayer(bgImage);
graphics().rootLayer().add(bgLayer);
CanvasImage canvasImage = graphics().createImage(graphics().width(), graphics().height());
ImageLayer imageLayer = graphics().createImageLayer();
imageLayer.setImage(canvasImage);
graphics().rootLayer().add(imageLayer);
canvas = canvasImage.canvas();
}
#Override
public void paint(float alpha) {
canvas.clear();
canvas.drawText("PlayN is cool!", x++, 20);
canvas.drawText("Hello World", 20, 100);
}
#Override
public void update(float delta) {
}
#Override
public int updateRate() {
return 25;
}
}
I would expect the text "PlayN is cool!" to move horizontally like in Java but it is not. I tried to debug but I cannot step in the native code.
Anyone has a clue? Am I trying something prohibited?

This is a known issue, as documented here. It only affects the HTML backend. That seems to be exactly what you're seeing.

Your code looks fine to me.
Do you wait for the image to load, because the case in safari is maybe it loads after the drawText and paints over it.. and in java probably the loading is fast enough so it loads fine.
care for the done() callback ... so it sets the text update after the image is loaded

Related

JavaFX: How can I detect end of rendering in TitledPane?

This is the background for my question:
I have a GUI with an accordion with many TitledPanes, and each Titledpane contains a spreadsheetView from the controlsFX package.
There is a search-function in the code, where a Titledpane is opened and a specific cell in the spreadsheetView is opened for text input using the edit method of the spreadsheetcell type.
If the TitledPane is already open, this works fine, but if it must open first then the call of the edit-method fails. (The program is actually written in scalafx, but I don't think that matters here because scalafx is just a wrapper of javaFX and calls all the javaFX methods.)
Someone from the scalafx user group found out, that when I put in a wait time of 350ms (The animation time of the TitledPane is 300ms) then the call of 'edit' on the cell succeeds. He thought that the call fails, when the rendering of the content of the TitledPane is not complete.
This is also true when I turn the animation for the TitledPane off. In this case, it is sufficient to wait for 50ms, which does not work when animation is on.
Anyway - I am concerned about just waiting 350ms and hoping that this will always work. Which brings me back to the question: How can I tell that the rendering inside the TitledPane (or the spreadsheetView?) is complete so that I can safely call my edit method on the spreadsheetView?
Astonishingly, that doesn't seem to be supported.
The property that changes during the expand/collapse phase is the content's height: so a hack around might be to listen to it and start editing when fully expanded (which is a bit hacky in itself, could change due to layout constraints as well).
The example below simply initializes the fully expanded height after showing, listens to content's height property and starts editing when it reaches the fully expanded height.
The code:
public class TitledPaneEndOfExpansion extends Application {
private DoubleProperty expandedHeight = new SimpleDoubleProperty();
private TitledPane titled = new TitledPane();
private Parent createContent() {
titled.setText("Titled");
ListView<String> list = new ListView<>(FXCollections.observableArrayList("some", "content"));
list.setEditable(true);
list.setCellFactory(TextFieldListCell.forListView());
titled.setContent(list);
list.heightProperty().addListener((src, ov, nv) -> {
if (nv.doubleValue() == expandedHeight.get()) {
list.edit(0);
}
});
BorderPane content = new BorderPane(titled);
return content;
}
#Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
stage.setTitle(FXUtils.version());
stage.show();
expandedHeight.set(((Region) titled.getContent()).getHeight());
}
public static void main(String[] args) {
launch(args);
}
}
Basically I like kleopatras idea, but unfortunately I can't figure out if this works for me or not.
At first I had some problems reading the code - only because my java knowledge is very limited. So I transferred it to scala. When I run it there, the call to edit works only sometimes (after startup it does not, when i clicked into a cell to edit it does). So I added a button that also calls edit - and it had the same behavior. So calling edit in general seems to have a problem in scalafx. But I learned something interesting here. I will now wait a few more days to see if anyone can think of anything else. If not then I will accept kleopatras solution.
For my own reference I add my not working scala-code here:
import scalafx.Includes._
import scalafx.application.JFXApp
import scalafx.beans.property.DoubleProperty
import scalafx.beans.value.ObservableValue
import scalafx.collections.ObservableBuffer
import scalafx.event.ActionEvent
import scalafx.scene.Scene
import scalafx.scene.control.cell.TextFieldListCell
import scalafx.scene.control.{Button, ListView, TitledPane}
import scalafx.scene.layout.BorderPane
object TitledPaneEndOfExpansion extends JFXApp {
val expandedHeight = new DoubleProperty()
val data: ObservableBuffer[String] = new ObservableBuffer[String]() ++= List("some", "content", "for", "testing")
stage = new JFXApp.PrimaryStage {
title = "JavaFX: edit after rendering test"
val list: ListView[String] = new ListView[String](data) {
editable = true
cellFactory = TextFieldListCell.forListView()
height.onChange { (source: ObservableValue[Double, Number], oldValue: Number, newValue: Number) =>
println("old height is: " + oldValue.doubleValue() + " new height is: " + newValue.doubleValue())
if (newValue.doubleValue() == expandedHeight.value) {
edit(1)
}
}
}
val titled: TitledPane = new TitledPane {
text = "titled"
content = list
}
scene = new Scene {
root = new BorderPane {
center = titled
bottom = new Button() {
text = "edit cell 1"
onAction = { _: ActionEvent => list.edit(1) }
}
}
}
expandedHeight.value = 400
list.edit(1)
}
}

Persistent header fragment (disable animation) in Android TV (leanback)

Anyone knows how to achieve the question in the title? The objective is to avoid the animation that results in the Headers bar disappearing as the Leanback app zooms in on the Row Item once a Header has been clicked.
setHeadersState of BrowseSupportFragment doesn't help. Perhaps something to do with hijacking startHeadersTransitionInternal during OnHeaderClickedListener? If so, any idea how to correctly implement it?
So the problem with this one is that the transition is handled by the method startHeadersTransitionInternal which is package private. Because of this, you can't override it in most situations. However, since it's only package private and not private private, there's a little hack around this that you can do.
First, make a package in your app with the same package name as BrowseSupportFragment. Then make a class in that package that extends BrowseSupportFragment and override the offending method with no implementation. That'd look something like this:
package android.support.v17.leanback.app; // Different for AndroidX
public class HackyBrowseSupportFragment extends BrowseSupportFragment {
#Override
void startHeadersTransitionInternal(boolean withHeaders) {
// Do nothing. This avoids the transition.
}
}
Then, instead of extending BrowseSupportFragment, you'd extend HackyBrowseSupportFragment.
One thing to note that I found with this is that the back button will no longer refocus the headers from one of the rows, so you'll have to do that manually. Other than that, seems to work just fine.
Following #MichaelCeley 's response and based on the original startHeadersTransitionInternal method from BrowseSupportFragment, this implementation that keeps the backstack and listeners works, too.
#Override
void startHeadersTransitionInternal(final boolean withHeaders) {
if (getFragmentManager().isDestroyed()) {
return;
}
if (!isHeadersDataReady()) {
return;
}
new Runnable() {
#Override
public void run() {
if (mBrowseTransitionListener != null) {
mBrowseTransitionListener.onHeadersTransitionStart(withHeaders);
}
if (mHeadersBackStackEnabled) {
if (!withHeaders) {
getFragmentManager().beginTransaction()
.addToBackStack(mWithHeadersBackStackName).commit();
} else {
int index = mBackStackChangedListener.mIndexOfHeadersBackStack;
if (index >= 0) {
FragmentManager.BackStackEntry entry = getFragmentManager().getBackStackEntryAt(index);
getFragmentManager().popBackStackImmediate(entry.getId(),
FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
}
}
}
}.run();
}

TranslateTransition in JavaFX does nothing

I'm trying to use a TranslateTransition object in JavaFX to move an onscreen object in a LOGO program I am building. I have an onscreen TurtleDisplay object, which extends ImageView, and this is what I'm trying to move. The code to move it is here:
public void drawTurtle(TurtleData currentData) {
TurtleImage inList = getTurtleImage(currentData);
if (inList==null) {
TurtleImage temp = new TurtleImage(currentData.getX(),
currentData.getY(), currentData.getHeading(), turtleImage);
myTurtlesGroup.getChildren().add(temp);
myTurtlesList.add(temp);
}
else {
TranslateTransition tt = new TranslateTransition(Duration.seconds(3),inList);
tt.setFromX(inList.getX());
tt.setFromY(inList.getY());
tt.setToX(inList.getX()+currentData.getX());
tt.setToY(inList.getY()+currentData.getY());
tt.setCycleCount(Timeline.INDEFINITE);
tt.play();
}
}
This code, which is part of the front end, is called from the back end via a Listener on an ObservableList. The backend contains this ObservableList of TurtleData objects that contain the information necessary to move a turtle on screen -- which turtle to move, the coordinate to move to, and the rotation of the turtle. The code to call this is here:
ObservableList<TurtleData> myTurtles = FXCollections
.observableArrayList();
myTurtles.addListener(new ListChangeListener<TurtleData>() {
#Override
public void onChanged(Change<? extends TurtleData> c) {
myDisplay.getSelectedWorkspace().getTV().clearTurtles();
while (c.next()) {
for (TurtleData addItem : c.getAddedSubList()) {
myDisplay.getSelectedWorkspace().getTV().drawTurtle(addItem);
}
}
}
});
I have stepped through with a debugger and ensured that this code is called -- specifically, the tt.play() line is run. Nothing moves on screen. Does anyone have any idea what is wrong? Do I need to setup an Animation Timeline? Thank you for any help!

Getting JavaFX WebEngine Interpreted Document

Calling the getDocument() method on the WebEngine object for me only returns the source retrieved from the server, without the JavaScript being executed (there are still elements). This is the kind of source you would see if you used "View Source" in Chrome. How do I retrieve the interpreted source with the JavaScript already run?
public Browser() {
WebView browser = new WebView();
final WebEngine webEngine = browser.getEngine();
webEngine.load("*******************************");
webEngine.getLoadWorker().stateProperty().addListener(new ChangeListener<State>() {
#Override
public void changed(ObservableValue<? extends State> ov, State oldState, State newState) {
if (newState == State.SUCCEEDED) {
Document doc = webEngine.getDocument();
printDocument(doc);
}
}
});
}
This works as expected for me. In this example, the div contains a text node with the text that is set by the Javascript function:
import javafx.application.Application;
import javafx.concurrent.Worker;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public class WebViewOnLoadExample extends Application {
#Override
public void start(Stage primaryStage) {
WebView webView = new WebView();
webView.getEngine()
.getLoadWorker()
.stateProperty()
.addListener((obs, oldState, newState) -> {
if (newState == Worker.State.SUCCEEDED) {
Document doc = webView.getEngine().getDocument();
showNodeContent(doc, 0);
}
});
BorderPane root = new BorderPane(webView);
primaryStage.setScene(new Scene(root, 600, 400));
primaryStage.show();
webView.getEngine().loadContent("<html>"
+"<head><script>"
+"function setText() {"
+" document.getElementById(\"target\").appendChild(document.createTextNode(\"Hello World\"));"
+"}"
+"</script></head>"
+"<body onload='setText()'>"
+"<div id='target'></div></body></html>");
}
private void showNodeContent(Node n, int depth) {
for (int i=0; i<depth; i++) {
System.out.print(" ");
}
System.out.println(n.getNodeName()+":"+n.getNodeValue());
NodeList children = n.getChildNodes() ;
for (int i=0; i<children.getLength(); i++) {
showNodeContent(children.item(i), depth+1);
}
}
public static void main(String[] args) {
launch(args);
}
}
The problem you are facing is the following: the LoadWorker's state is set to SUCCEEDED before JavaScript is done running. JavaScript does in fact run (as shown in #James_D's reply) but there is no callback to signal when it finishes. AFAIK, there is no reliable way to detect when the WebEngine is done executing JS.
What you could do as a workaround is play a PauseTransition after the state changes to SUCCEEDED, which can be abused to act like a sleep on the JavaFX thread (JS is executed in the background thread that also loads the Document, so JS will not pause). However, sleeping (to wait for JS to finish) is inherently a violation of JavaFX's core principle never to block the UI thread. On top of that, waiting for a period of time does not guarantee that JS is done executing before that period passes.
I've faced the same problem and I have not found a decent solution. Let me know if you do!
I'm not sure if I'm getting your question right, but if you are looking for a way go print the visible content of the web you are loading, getting the DocumentElement from Document will allow you to dive into its structure and filter what you need.
This method will print the content of the desired tags:
private void printElement(Element el, int level){
NodeList childNodes = el.getChildNodes();
for(int j=0; j<level; j++) System.out.print("-");
System.out.print("tag: "+el.getNodeName());
if(el.getNodeName().equals("A")){
System.out.print(", content: "+el.getTextContent());
}
System.out.println("");
for(int i=0; i<childNodes.getLength(); i++){
Node item = childNodes.item(i);
if(item instanceof Element){
printElement((Element)item, level++);
}
}
}
so once you've loaded the URL, just call it:
if(newState==State.SUCCEEDED){
Document doc = webEngine.getDocument();
Element el = doc.getDocumentElement();
printElement(el,0);
}
This will print all the DOM tags with their level of indentation, and for the tag specified, it will print also the content. In this case, with the tag "A" it will print the content of all the links.
I'm not sure if this will help. Please clarify your question otherwise.

How to create a custom loading screen in JavaFX?

I'd like to create a custom loading screen for a JavaFX application. Don't want the user to see the Java coffee cup icon, I want to put my own graphic there!
I've found out how to provide a static image, or even an animated GIF, but I'm more interested in a Flash-like screen where I can specify what the state of the image looks like at certain percentages.
Any ideas?
For JavaFX2, you can set a custom preloader. You have complete control over then scene. I haven't used them personally, but this might be what you want.
http://docs.oracle.com/javafx/2/deployment/preloaders.htm
JavaFX preloader class
I have created a very simple preloader screen using native JavaFX APIs. Here it's explained how to do this: https://docs.oracle.com/javafx/2/deployment/preloaders.htm (old but workable examples) - this is newer and seems to be the same: https://docs.oracle.com/javase/8/docs/technotes/guides/deploy/preloaders.html (Newer page and JavaFX version but I don't see the difference).
The older link is easier to read, because of page formatting.
Main class
package YOUR_PACKAGE_NAME;
import javafx.application.Application;
/**
* Minimal reproducible example (MRE) - Example of a simple JavaFX preloader.
* Java Main class for starting up the JavaFX application with a call to launch MainApplication.
* #author Remzi Cavdar - ict#remzi.info - #Remzi1993
*/
public class Main {
public static void main(String[] args) {
/*
* The following Java system property is important for JavaFX to recognize your custom preloader class.
* Which should extend javafx.application.Preloader.
*/
System.setProperty("javafx.preloader", Preloader.class.getName());
// Launch the main JavaFX application class.
Application.launch(MainApplication.class, args);
}
}
Preloader class
package YOUR_PACKAGE_NAME;
import javafx.scene.Scene;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
/**
* Minimal reproducible example (MRE) - Example of a simple JavaFX preloader class.
* #author Remzi Cavdar - ict#remzi.info - #Remzi1993
*/
public class Preloader extends javafx.application.Preloader {
private ProgressBar progressBar;
private Stage stage;
private Scene createPreloaderScene() {
progressBar = new ProgressBar();
BorderPane borderPane = new BorderPane();
borderPane.setCenter(progressBar);
return new Scene(borderPane, 800, 600);
}
#Override
public void start(Stage stage) throws Exception {
this.stage = stage;
// I also recommend to set app icon: stage.getIcons().add();
stage.setTitle("YOUR TILE HERE");
stage.setScene(createPreloaderScene());
stage.show();
}
#Override
public void handleProgressNotification(ProgressNotification pn) {
progressBar.setProgress(pn.getProgress());
}
#Override
public void handleStateChangeNotification(StateChangeNotification evt) {
if (evt.getType() == StateChangeNotification.Type.BEFORE_START) {
stage.hide();
}
}
}
Testing
Tested on: 01-11-2022
Tested OS: Windows 11 - Version 21H2 (OS Build 22000.1098)
Tested with: OpenJDK 19 - Eclipse Temurin JDK with Hotspot 19.0.1+10 (x64) (See: https://adoptium.net/en-GB/temurin/releases/?version=19)
Tested with JavaFX (OpenJFX) version: OpenJFX 19 (See: https://openjfx.io and repo: https://github.com/openjdk/jfx)
If you're setting things up as shown on This blog entry, it looks like the answer would be 'no' - the loading graphic is just part of the overall options that are passed to the applet. Because this applet could be any java code (not just javaFX), there's no way to tie your custom renderer in.
you should use java timer:
Timer tm= new Timer();
Stage ilk;
int count;
public void check() {
ilk=new Stage();
TimerTask mission;
gorev = new TimerTask() {
#Override
public void run() {
Group root = new Group();
Scene scene;
scene = new Scene(root, 960, 540);
scene.setFill(Color.BLACK);
ilk.setScene(scene);
ilk.setTitle("Splash Screen");
sayac++;
if(count==5){
tm.cancel();
ilk.show();
}
}
};
tm.schedule(mission, 0, 2000);
}
For changing the coffee cup icon:
stage.getIcons().add(new Image("images/myimage.png"));
and here is a reference for a very clear preloader screen out there and awesome css too:
http://docs.oracle.com/javafx/2/best_practices/jfxpub-best_practices.htm

Resources