Search code examples
javaswtnattable

Drawing animation in a Nattable OverlayPainter


I am trying to render a loading animation on top of a nattable. I use the OverLayPainter mechanism to draw a "glasspane" and some text on top of the table, and this works perfect:

public class MessageOverlay
implements IOverlayPainter {
....
@Override
public void paintOverlay(final GC gc, final ILayer layer) {
  this.currentGC = gc;
  this.currentLayer = layer;
  if (visible) {
    currentGC.setAlpha(200);
    currentGC.fillRectangle(0, 0, currentLayer.getWidth(), currentLayer
      .getHeight());
     drawMessage();

    if (withLoadingAnimation) {
      showAnimation = true;
    }
   } else {
     showAnimation = false;
   }
  }
}

However, the paintOverlay method is not called regularely but rather everytime the table changes.

To be able to display a smoothe animation, I added a new thread

final Thread animatorThread = new Thread(new Runnable() {

  @Override
  public void run() {

    while (!Thread.interrupted()) {
      try {
        Thread.sleep(1000 / fps);
      } catch (final InterruptedException e) {
        break;
      }

       display.asyncExec(new Runnable() {
        @Override
        public void run() {
          if (showAnimation && !currentGC.isDisposed()) {
            final Image currentImage = getNextImage();
            final int posX = currentGC.getClipping().width / 2
                - currentImage.getBounds().width;
            final int posY = currentGC.getClipping().height / 2
                - currentImage.getBounds().height;
            currentGC.drawImage(currentImage, posX, posY);
          }
        }
      });
    }
  }
});

animatorThread.start();

As you can see it tries to access the graphics context this.currentGC set in the paintOverlay method. My problem is, that currentGC within the animatorThread is always disposed.

How can I a.) ensure that the context is not disposed in the thread or b.) solve this in an alternative way?

Thanks for the help.


Solution

  • You could try to create a new GC with the current NatTable instance and if needed pass the config from the passed in GC instance. Then you are in charge of disposing the GC instance and should not have the risk of a disposed GC outside your thread.

    A simple example could look like the following snippet that simply shows the pane for 1000ms and then removes it again. You need of course to change the logic to be more dynamic with regards to your loading operation then:

    AtomicBoolean paneThreadStarted = new AtomicBoolean(false);
    
    ...
    
    natTable.addOverlayPainter(new IOverlayPainter() {
    
        @Override
        public void paintOverlay(GC gc, ILayer layer) {
            if (this.paneThreadStarted.compareAndSet(false, true)) {
                Display.getDefault().asyncExec(new Runnable() {
    
                    @Override
                    public void run() {
                        GC currentGC = new GC(natTable);
                        currentGC.setForeground(GUIHelper.COLOR_WHITE);
                        currentGC.setBackground(GUIHelper.COLOR_BLACK);
                        currentGC.setAlpha(200);
                        currentGC.fillRectangle(0, 0, layer.getWidth(), layer.getHeight());
    
                        String load = "Loading data ...";
                        Point textExtent = currentGC.textExtent(load);
                        currentGC.drawText(load,
                                layer.getWidth() / 2 - textExtent.x / 2,
                                layer.getHeight() / 2 - textExtent.y / 2,
                                true);
    
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        currentGC.dispose();
                        natTable.redraw();
                    }
                });
            }
        }
    });
    

    This way you are able to show the pane again by changing the AtomicBoolean from the outside:

        Button showPaneButton = new Button(buttonPanel, SWT.PUSH);
        showPaneButton.setText("Show Pane");
        showPaneButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                this.paneThreadStarted.set(false);
                natTable.redraw();
            }
        });