I know that you can track the window resize operation using:
glfwSetWindowSizeCallback(window, wsCallback = new GLFWWindowSizeCallback() {
#Override
public void invoke(long window, int w, int h) {
LOG.info("window resized");
if (w > 0 && h > 0) {
width = w;
height = h;
}
}
});
However, this way the invoke method gets invoked potentially Hundreds of times, and I only want the final event to store the new size in the configuration. How do I do this without using some sort of delay mechanism like a one second timer that gets refreshed on further invoke calls?
The way GLFW callbacks are set up is so they are refreshed upon each call of glfwPollEvents(). If you only want to set the configuration variables on the final update, this in and of itself is not feasible. I would have a void dispose() method in which you can call this:
public void dispose() {
try (MemoryStack stack = stackPush()) {
IntBuffer width = stack.ints(1);
IntBuffer height = stack.ints(1);
glfwGetWindowSize(windowID, width, height);
configuration.width = width.get();
configuration.height = height.get();
}
}
This allows for you to set the configuration data once when you want to close the window. The draw back to this technique is if the application crashes or the dispose() method isn't called, the configuration data isn't saved.
Related
I am currently having some trouble when running the follwing code. If I delete this part the problems disappear so this part of my whole code has to be the problem. It runs and draws what I want perfectly but after a few seconds (maxAddedRuntime is set via user (milliseconds)) the application freezes for a while (window is not responding Windows message) and starts over with drawing after waiting approximately the same time while the window is frozen. What do I do wrong?
I am using SWT and a canvas to draw. Thank you for your help
public void drawNetwork(Canvas canvas, GC gc, Network network, Shell shlNetworkVisualizer) {
startTime = System.currentTimeMillis();
endTime = startTime + maxAddedRuntime;
this.drawNetworkAlg1(canvas, gc, network);
int canvasHeight = canvas.getBounds().height;
int canvasWidth = canvas.getBounds().width;
while (System.currentTimeMillis()<endTime) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
gc.fillRectangle(0, 0, canvasWidth, canvasHeight); //ClearCanvas basically
for (Nodek: network.node) {
//drawSomeStuff
}
for (Edge k: network.edges) {
//alsoDrawSomeStuff
}
}
}
An SWT app must return to the main Display.readAndDispatch loop as quickly as possible. So you cannot use a loop with a Thread.sleep call - this will just lock up the UI until the loop ends.
Instead you can use Display.timerExec to run code after a delay. You would use this to run a single step (just one gc.fillRectange for example) and then call Display.timerExec again to schedule the next step.
public void timerExec(int milliseconds, Runnable runnable)
Note: The GC you receive from a paint event is only valid during the paint. The timerExec call should normally just call redraw on the canvas to cause a new paint event to be generated.
Here is a simple class that does basic timerExec calls and paints:
class Progress
{
private final Canvas canvas;
private final long endTime;
Progress(Canvas c)
{
canvas = c;
endTime = System.currentTimeMillis() + 1_000;
canvas.addListener(SWT.Paint, this::paint);
canvas.getDisplay().timerExec(100, this::timer);
}
private void paint(Event event)
{
GC gc = event.gc;
int canvasHeight = canvas.getBounds().height;
int canvasWidth = canvas.getBounds().width;
gc.fillRectangle(0, 0, canvasWidth, canvasHeight);
// TODO more painting
}
private void timer()
{
if (canvas.isDisposed()) { // Don't continue if control closed
return;
}
canvas.redraw();
if (System.currentTimeMillis() < endTime) {
canvas.getDisplay().timerExec(100, this::timer);
}
}
}
Before you read, this will be informative: Java JFrame won't show up after using .setVisible(true) after being invisible
Hello I am working on a library API that let's you capture an area of the screen, and it returns you a class that contains the ByteArrayInputStream and utility methods like createBufferedImage, createFile, etc.
Basically you create a Bootstrap instance, and pass the capturer type you want as a dependency (ScreenshotCapturer or GifCapturer):
Bootstrap b = new Bootstrap(new ScreenshotCapturer());
And the beginCapture method receives an object that implements ScreenCaptureCallback which is the callback event that the captured result will be passed to.
This is a short background.
Now when you use the beginCapture method, basically what it does is creates new instance of SelectionCamera, this is basically the component that paints the selection area you're selecting when dragging the mouse, and updates the listeners.
once created instance, it calls super.setVisible(true);
After that method gets called, the frame will show up, and also show the old painted screen for like 600-500miliseconds, I am not exactly sure, but it disappears so quickly.
Take a look at this live example:
Note use the video option, otherwise you will not see what I'm seeing as gif is too slow to show it!
http://gyazo.com/d2f0432ada37842966b42dfd87be4240
You can see after I click Screenshot again, it shows the old selected area and disappears quickly. (by the way the frame you see in the gif is not part of the app, just dummy hello world example usage).
The process of image capture.
Step 1
beginCapture gets called:
public void beginCapture(final ScreenCaptureCallback c) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
capturer.setCallback(c);
capturer.beginSelection();
}
});
}
Step 2
beginSelection gets called in the Capturer class (ScreenshotCapturer extends Capturer (abstract)
#Override
public void beginSelection() {
super.init();
this.setHotkeys();
super.getCamera().startSelection();
}
Step 3
CaptureCamera#startSelection() gets called
public void startSelection() {
super.getContentPane().removeAll();
super.getContentPane().repaint();
super.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
this.selector = new SelectionCamera();
this.selectionMosueAdapter.updateCamera(this.selector);
this.selectionMouseMotion.updateCamera(this.selector);
super.add(this.selector);
super.setVisible(true);
super.repaint();
super.getContentPane().repaint();
}
Step 4
The user selects an area, and both mouse listener and mouse motion listens to it(Take a look at mouse motion):
#Override
public void mouseDragged(MouseEvent e) {
Point dragPoint = e.getPoint();
Point startPoint = this.selector.getStartPoint();
int x = Math.min(startPoint.x, dragPoint.x);
int y = Math.min(startPoint.y, dragPoint.y);
int width = Math.max(startPoint.x - dragPoint.x, dragPoint.x - startPoint.x);
int height = Math.max(startPoint.y - dragPoint.y, dragPoint.y - startPoint.y);
this.selector.setCameraDimension(width, height);
this.selector.setCoordinates(x, y);
this.camera.repaint(); // important
}
by the way this.selector is SelectorCamera which is the component that paints the selection area.
Step 5
CaptureCamera#endSelection() gets called, this method gets the x,y, width, height from the selection camera and passes it to the capturer class which uses Robot to get screenshot with that rectangle, and before that it removes ALL components from the content pane, and repaints everything and then sets visibility to false.
public void endSelection() {
super.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
int x = this.selector.getCameraX();
int y = this.selector.getCameraY();
int w = this.selector.getCameraWidth();
int h = this.selector.getCameraHeight();
super.getContentPane().removeAll();
super.getContentPane().repaint();
//super.repaint();
super.setVisible(false);
this.c.startCapturing(x, y, w, h);
}
Basically this is the last step, rest steps are unnecessary for the debugging as it only sends back the callback.
I really tried my best explaining the process of my application, I've tried figuring it out for 5 and half hours now, and no luck at all. Tried different ways, by creating new SelectionCamera object as you see, doesn't work.
Why is it doing this? Is it something to do with the swing core?
SelectionCamera code: https://github.com/BenBeri/WiseCapturer/blob/master/src/il/ben/wise/SelectionCamera.java
Thanks in advance.
Based on this example...
try {
final Bootstrap b = new Bootstrap(new ScreenshotCapturer());
b.beginCapture(new ScreenCaptureCallback() {
#Override
public void captureEnded(CapturedImage img) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
b.beginCapture(new ScreenCaptureCallback() {
#Override
public void captureEnded(CapturedImage img) {
System.out.println("...");
JFrame frame = new JFrame();
frame.add(new JLabel(new ImageIcon(img.getBufferedImage())));
frame.pack();
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
});
}
});
}
});
System.out.println("Hello");
} catch (AWTException exp) {
exp.printStackTrace();
}
I won't focus on the initialise stage of the first round, I will focus on the initialisation of the second round as this is where the problem is...
b.beginCapture call's this.capturer.beginSelection();, which calls super.getCamera().startSelection(); which calls setVisible(true) (CaptureCamera been a JFrame).
This will immediately show what ever was previously displayed on the CaptureCamera. It's important to note here, that no new instances of objects were created through the process...
Now, I made a lot of changes to the base testing this, but it appears that the problem is with the restoration of the frame when it's made visible for the second time. This seems to be an issue with the transparency support of the Window as it seems to restore the last "known" state instead of repainting the window immediately...
Now, I tried clearing the selector before making the CaptureCamera invisible to no eval, as the window seems to be made invisible before the selector is painted.
The final solution I came up with was to call dispose on the CaptureCamera, which releases it's native peer and therefore destroys the old graphics context, forcing the frame to rebuild itself when it is made visible again.
"A" problem with this could be the fact that when all the windows are disposed (and the only running threads are daemon threads), the JVM will exit...
This was an issue during my testing as I was using a javax.swing.Timer to put a delay between the first and second capture process so I could see where the problem was occurring and this caused my JVM to exit (as the timer uses a daemon thread and I had no other windows open).
I got around this by creating a tiny (1x1) transparent window in the Capturer class, keep this in mind if the JVM exists gracefully for no reason ;)
Side Notes...
Now, there is an issue with SelectionCamera (which extends JPanel), it is opaque, but is using a transparent background, this is incredibly dangerous as Swing only knows how to deal with opaque or fully transparent components.
public SelectionCamera() {
super.setBackground(new Color(0, 0, 0, 0));
super.setVisible(false);
}
Should be updated to something like...
public SelectionCamera() {
setOpaque(false);
//super.setBackground(new Color(0, 0, 0, 0));
super.setVisible(false);
}
I'm also confused over the use of super.xxx, the only reason you would do this is if you had overrriden those methods and didn't want to call them at this time...In my testing, I removed all the calls to super where a method wasn't overridden in the current class (and I wasn't already in the overriden method)
Also, the paintComponent method should be calling super.paintComponent
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(new Color(0, 0, 0, 0.5f));
g.fillRect(this.x, this.y, this.width, this.height);
}
Make Frame left to be -10,000 then set visible true, add a timer 2 seconds (try lower to 25-100 milliseconds, just to give it sligth pause to invalidate content) , on timer :left to 0 . I think it works due to caching & double buffereing. Frame shows what it had in buffer, buffer points to old image due to caching/ lazy repaint.
Alternative :
Maybe a repaint or invalidate before your show would work too, and don't need to do the left -10,000. I dont work much with ui-swing, just a but years back and remember some strange things like this.
For organization's sake, I use multiple scenes for my game and rather than having each scene have a constructor that receives a Viewport (my game is scalable), I would like to set each stage's viewport separate of the constructor, then after the viewport is set, add the actors. In the main class, it would happen like this:
public void setStage(Stage s)
{
if(currentStage != null)
currentStage.dispose();
currentStage = s;
currentStage.setViewport(view);
}
To make this go fluidly, each stage has an init method that is called within an overriden setViewport:
#Override
public void setViewport(Viewport v)
{
super.setViewport(v);
init();
}
However, all this gives me is a black screen... I have tried updating the camera and viewport, but no avail (note that the actors are having their render methods called).
Why am I getting this black screen and how do I fix it? If it's not possible I'll just revert to using the constructor.
If I understood correctly you want to do this:
Stage stage1 = new Stage();
stage1.getViewport().update(width, height);
rather than this:
Stage stage1 = new Stage (new StretchViewport(width, height)); // It doesn't have to be StretchViewport
In the first case (what you are trying to do) a ScalingViewport will be costructed automatically for you with dimensions of the Gdx.graphics and an orthographic camera and acts like a StretchViewport. Why not using the second case directly where you pass the viewport you want. You can always alter your viewport whenever you want by calling stage1.getViewport().update(width, height);
or by calling stage1.setViewport(width, height, false); in older Libgdx versions.
Viewport has changed recently so if you can extend Viewport class to Override the update method maybe you can achieve what you want:
public class ViewportExtendClass extends StretchViewport{
public ViewportExtendClass(float worldWidth, float worldHeight) {
super(worldWidth, worldHeight);
}
#Override
public void update (int screenWidth, int screenHeight, boolean centerCamera) {
super.update(screenWidth, screenHeight, centerCamera);
// DO YOUR INITIALIZATION HERE
}
}
From your main class you create new stage :
Stage stage1 = new Stage (new ViewportExtendClass (width, height));
and then you call :
stage1.getViewport().update(width, height);
Like this you can alter stage viewport and re initialize your assets.
#Override
public void setViewport(Viewport v)
{
super.setViewport(v);
this.getViewport().update(Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), false);
Camera c = this.getViewport().getCamera();
c.position.set(c.viewportWidth/2, c.viewportHeight/2, 0);
init();
}
This works, but you should also be able to update the Viewport like that at the begin of your application, if you continue to use the same one. I set the position like that instead of centering because some of my Stages will be larger than the screen.
I need specific behaviour when my custom control resized. When control resized(resize or resizeRelocate methods called) first I want just scale child nodes to fit new bounds. And if there is no size changes in i.e. 1 second - make expensive calculations and relayout of child nodes. If I recalculate on every resize call - it makes Stage resizing very laggy. How can I achieve that?
This is example, here CurvePlot is just data model:
class ShapeCurvePlot extends Polyline {
private final CurvePlot model;
public ShapeCurvePlot(CurvePlot model) {
Objects.requireNonNull(model);
this.model = model;
strokeProperty().bind(model.strokeProperty());
strokeWidthProperty().bind(model.strokeWidthProperty());
}
#Override
public boolean isResizable() {
return true;
}
#Override
public void resize(double width, double height) {
// "expensive calculation"
Series series = model.getSeries();
double xOffset = series.valueAxis().getLeft();
double yOffset = series.keyAxis().getLeft();
double yScale = height / series.keyAxis().range();
double xScale = width / series.valueAxis().range();
getPoints().clear();
for (Map.Entry<Double, Double> item : series.data().entrySet()) {
double x = item.getValue();
double y = item.getKey();
getPoints().addAll((x - xOffset) * xScale, (y - yOffset) * yScale);
}
}
}
While in this example resize works fast, other shapes, that I need, not so easy to recalculate. I'm asking for generic solution to delay calculation until there was no resizing within X seconds.
ps. sorry for my english, I'm not native speaker..
I have come across this issue before, both in C# and Java. My solution, not saying that it is the best solution, but it seems to work, is to use some form of timer that triggers the expensive re-calculation.
In my situation, I have used this in search text boxes, the actual search is only triggered after the user has not pressed a key for some time (say 500ms), preventing the triggering of the search for every key stroke.
In your situation, you could trigger the timer within the resize() method. When the timer runs out, it then performs the expensive operation. Triggering the timer while it is already waiting results in the timer being reset.
In java I did this by using a java.util.Timer for the timer which would then run a java.util.TimerTask that I created. Within the run() method of the TimerTask, I created a javafx.concurrent.Task and ran that in a new Thread. The call() method of the Task is where the work was done. This ensured that the work was done on the JavaFX thread, otherwise you get threading issues.
Hopefully this is some help to you.
Edit ... here's some code that should do what I was talking about above. Note: This code is completely untested, I think it should work though
class ShapeCurvePlot extends Polyline {
private class ResizeTimerTask extends TimerTask {
private ShapeCurvePlot plot;
public ResizeTimerTask(ShapeCurvePlot plot) {
this.plot = plot;
}
/* (non-Javadoc)
* #see java.util.TimerTask#run()
*/
#Override
public void run() {
Task<Object> t = new Task<Object>() {
#Override
protected Object call() throws Exception {
// "expensive calculation"
Series series = plot.getSeries();
double xOffset = series.valueAxis().getLeft();
double yOffset = series.keyAxis().getLeft();
double yScale = height / series.keyAxis().range();
double xScale = width / series.valueAxis().range();
plot.getPoints().clear();
for (Map.Entry<Double, Double> item : series.data().entrySet()) {
double x = item.getValue();
double y = item.getKey();
plot.getPoints().addAll((x - xOffset) * xScale, (y - yOffset) * yScale);
}
}
}
new Thread(t).start();
}
}
private static int RESIZE_DELAY_MS = 1000;
private final CurvePlot model;
private Timer resizeTimer;
public ShapeCurvePlot(CurvePlot model) {
Objects.requireNonNull(model);
this.model = model;
strokeProperty().bind(model.strokeProperty());
strokeWidthProperty().bind(model.strokeWidthProperty());
}
#Override
public boolean isResizable() {
return true;
}
public Series getSeries() {
return this.model.getSeries();
}
#Override
public void resize(double width, double height) {
// cancel the current task (if any).
if(this.resizeTimer != null) {
this.resizeTimer.cancel();
this.resizeTimer.purge();
this.resizeTimer = null;
}
try {
// create a new task that will be executed in 1 second.
this.resizeTimer = new Timer();
this.resizeTimer.schedule(new ResizeTimerTask(this), RESIZE_DELAY_MS);
} catch (OutOfMemoryError oom) {
oom.printStackTrace();
if(this.resizeTimer != null) {
this.resizeTimer.cancel();
this.resizeTimer.purge();
this.resizeTimer = null;
}
}
}
}
Further Edit ... Just spent some of today playing with JavaFX threading issues, there is another option that you could use for running the time consuming task in the JavaFX display thread. Rather than using a Task, you could execute a Runnable in the JavaFX thread using javafx.application.Platform.runLater(Runnable r). This will execute the runnable some time in the future, with the benefit that all runnables that are run in this way are processed in FIFO order. This can be handy to prevent things from getting out of order while still being able to run them in an async manner on the JavaFX display thread.
To do that, you would change the implementation of the run() method of the ResizeTimerTask class to:
public void run() {
Platform.runLater(new Runnable() {
#Override
public void run() {
// "expensive calculation"
Series series = plot.getSeries();
double xOffset = series.valueAxis().getLeft();
double yOffset = series.keyAxis().getLeft();
double yScale = height / series.keyAxis().range();
double xScale = width / series.valueAxis().range();
plot.getPoints().clear();
for (Map.Entry<Double, Double> item : series.data().entrySet()) {
double x = item.getValue();
double y = item.getKey();
plot.getPoints().addAll((x - xOffset) * xScale, (y - yOffset) * yScale);
}
}
});
}
I am working on an Android app that displays cell phone usage information in a progress bar. The bar changes color based on the amount of usage from green to yellow to red. When my TimerTask executes the update though (via a Handler so it goes through the UI thread and not the Timer thread), the progress bars empty, even though the text labels are updated correctly. The code updating the progress bar is:
private void SetBarColor(ProgressBar bar, int progress, int secondaryAdditive){
int setTo = R.drawable.greenbar;
if(progress < 60)
setTo = R.drawable.greenbar;
else if (progress < 90)
setTo = R.drawable.yellowbar;
else
setTo = setTo = R.drawable.redbar;
bar.setProgress(progress);
bar.setSecondaryProgress(progress + secondaryAdditive); //Mocking up phone usage
Rect bounds = bar.getProgressDrawable().getBounds();
bar.setProgressDrawable(this.getResources().getDrawable(setTo));
bar.getProgressDrawable().setBounds(bounds);
bar.setVisibility(View.VISIBLE);
}
This method works fine when called the first time in the onCreate method, but when called from the TimerTask, the bars simply hide themselves, showing only the grey background (as if their progress == 0). I've used the debugger and confirmed that the right values are going into the setProgress and setSecondaryProgress() calls. I have also tried setting the progress both before (as in the snippet above) and after the setProgressDrawable call, to no avail.
Anyone run into something like this?
EDIT: By request, some additional code. Here's the Runnable:
private class MyTime extends TimerTask {
#Override
public void run() {
ReQueryCount--;
if(ReQueryCount <= 0){
ReQueryCount = ReQueryCountStarter;
HeartbeatHandler.post(new Runnable() {
public void run() {
GetDataFromServer();
}
});
}
}
}
The HeartbeatHandler is created in onCreate.
GetDataFromServer gets some data from server, but the part that consumes my SetBarColor above is:
private void UpdateProgressBars(ServiceHelper.UsageResult result) {
int voiceBarProg = (int)((double)result.VoiceUsage / (double)result.MaxVoice * 100);
int dataBarProg = (int)((double)result.DataUsage / (double)result.MaxData * 100);
int msgBarProg = (int)((double)result.TextUsage / (double)result.MaxText * 100);
SetBarColor(voiceBar, voiceBarProg, PhoneVoice);
SetBarColor(dataBar, dataBarProg, PhoneData);
SetBarColor(msgBar, msgBarProg, PhoneText);
}
Short of posting the layouts, manifest and the rest I'm not sure what other code would be helpful.
After searching for hours for a solution of this problem I have found this article that has an answer that has helped me.
But in addition to the answer there I have also get/set the drawable bounds too. So I have a custom ProgressBar which has the following method to set progress color and amount:
public void setProgressColorAndFill(int color, int fill){
Drawable bgDrawable = new GradientDrawable(Orientation.TOP_BOTTOM, new int[]{0xFFdadada, 0xFFdadada});
Drawable proDrawable = new GradientDrawable(Orientation.TOP_BOTTOM, new int[]{color, color});
ClipDrawable clip = new ClipDrawable(proDrawable, Gravity.LEFT,ClipDrawable.HORIZONTAL);
Drawable[] layers = new Drawable[2];
layers[0] = bgDrawable;
layers[1] = clip;
LayerDrawable layerDrawable = new LayerDrawable(layers);
layerDrawable.setId(0, android.R.id.background);
layerDrawable.setId(1, android.R.id.progress);
Rect bounds = ((LayerDrawable)getProgressDrawable()).getBounds();
setProgressDrawable(layerDrawable);
getProgressDrawable().setBounds(bounds);
setProgress(1);
setMax(100);
setProgress(fill);
}
Hope this will be useful to someone.