Java SWT : Badge Notifications
Asked Answered
D

2

5

I have a desktop based UI application written in Java SWT running on windows.

I want to add a button on the UI screen whose behaviour should be similar to the badges on an iphone or facebook notifications as shown in the images below.

The number on the badge will be dynamic and will increase or decrease based on the number of pending notifications.

How can I implement something similar in SWT/AWT?

IOS Badge:

enter image description here

Facebook Notification:

enter image description here

Dorran answered 6/3, 2015 at 6:32 Comment(1)
There is no such widget in SWT that I know of. But you could write a custom widget that uses a paint listener to draw the image and the number overlay.Volga
K
6

I've implemented something like that recently. You can simply paint a custom image with GC, and the overlay on your desired icon.

I'm including my helper class here. It's not the cleanest code (a lot of stuff is hardcoded), but you'll get the point. The notification bubble resizes itself depending on the number of notifications (max 999).

How to use (Remember to cache and/or dispose your images!):

Image decoratedIcon = new ImageOverlayer()
        .baseImage(baseImage) // You icon/badget
        .overlayImage(ImageOverlayer.createNotifImage(5)) // 5 notifications 
        .overlayImagePosition(OverlayedImagePosition.TOP_RIGHT)
        .createImage();


/**
 * <pre>
 * The difference between this and the ImageBuilder is 
 * that ImageOverlayer does not chain the images, rather
 * just overlays them one onto another.
 * 
 * 
 * Rules:
 * 
 * 1.) Images are not disposed. Resource handing must be done externally.
 * 2.) Only two images allowed, for now.
 * 3.) The size of the composite image should normally be the size of the
 *     base image, BUT: if the overlaying image is larger, then larger
 *     parameters are grabbed, and the base image is still underneath.
 * 4.) Use the builder APIs to set the base and overlaying images. The 
 *     position of the overlaying image is optional, and CENTER by default.
 *     When you've set these, simply call createImage()
 * 
 * Further improvements:
 * 
 * - Combine this with ImageBuilder. These two composers should be welded.
 * 
 * </pre>
 * 
 * @author [email protected]
 *
 */
public class ImageOverlayer extends CompositeImageDescriptor
{

    // ==================== 1. Static Fields ========================

    public enum OverlayedImagePosition
    {
        TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT, CENTER;
    }


    // ====================== 2. Instance Fields =============================

    private ImageData baseImageData;

    private ImageData overlayedImageData;

    private OverlayedImagePosition overlayedImagePosition = OverlayedImagePosition.CENTER;



    // ==================== 3. Static Methods ====================

    /**
     * Creates a red circle with a white bold number inside it.
     * Does not cache the final image.
     */
    public static final Image createNotifImage(final int numberOfNotifications)
    {
        // Initial width and height - hardcoded for now
        final int width = 14;
        int height = 14;

        // Initial font size
        int fontSize = 100;

        int decorationWidth = width;

        String textToDraw = String.valueOf(numberOfNotifications);

        final int numberLength = Integer.toString(numberOfNotifications).length();

        if(numberLength > 3)
        {
            // spetrila, 2014.12.17: - set a width that fits the text
            //                       - smaller height since we will have a rounded rectangle and not a circle
            //                       - smaller font size so the new text will fit(set to 999+) if we have
            //                         a number of notifications with more than 3 digits
            decorationWidth += numberLength * 2;
            height -= 4;

            fontSize = 80;
            textToDraw = "999+"; //$NON-NLS-1$
        }
        else if (numberLength > 2)
        {
            // spetrila, 2014.12.17: - set a width that fits the text
            //                       - smaller height since we will have a rounded rectangle and not a circle
            decorationWidth += numberLength * 1.5;
            height -= 4;
        }

        final Font font = new Font(Display.getDefault(), "Arial", width / 2, SWT.BOLD); //$NON-NLS-1$

        final Image canvas = new Image(null, decorationWidth, height);

        final GC gc = new GC(canvas);

        gc.setAntialias(SWT.ON);
        gc.setAlpha(0);
        gc.fillRectangle(0, 0, decorationWidth, height);

        gc.setAlpha(255);
        gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_RED));

        // spetrila, 2014.12.17: In case we have more than two digits in the number of notifications,
        //                       we will change the decoration to a rounded rectangle so it can contain
        //                       all of the digits in the notification number
        if(decorationWidth == width)
            gc.fillOval(0, 0, decorationWidth - 1, height - 1);
        else
            gc.fillRoundRectangle(0, 0, decorationWidth, height, 10, 10);

        final FontData fontData = font.getFontData()[0];
        fontData.setHeight((int) (fontData.getHeight() * fontSize / 100.0 + 0.5));
        fontData.setStyle(SWT.BOLD);

        final Font newFont = new Font(Display.getCurrent(), fontData);

//      gc.setFont(AEFUIActivator.getDefault().getCustomizedFont(font, fontSize, SWT.BOLD));
        gc.setFont(newFont);
        gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE));

        final Point textSize = gc.stringExtent(textToDraw);
        final int xPos = (decorationWidth - textSize.x) / 2;
        final int yPos = (height - textSize.y) / 2;
        gc.drawText(textToDraw, xPos + 1, yPos, true);

        gc.dispose();

        final ImageData imgData = canvas.getImageData();

        // Remove white transparent pixels
        final int whitePixel = imgData.palette.getPixel(new RGB(255,255,255));
        imgData.transparentPixel = whitePixel;
        final Image finalImage = new Image(null, imgData);

        canvas.dispose();
        font.dispose();
        newFont.dispose();

        return finalImage;
    }


    // ==================== 5. Creators ====================

    @Override
    public Image createImage()
    {
        if (baseImageData == null || overlayedImageData == null)
            throw new IllegalArgumentException("Please check the ImageOverlayer. One of the overlaying images is NULL."); //$NON-NLS-1$

        return super.createImage();
    }


    // ==================== 6. Action Methods ====================

    @Override
    protected void drawCompositeImage(final int width, final int height)
    {
        /*
         * These two determine where the overlayed image top left
         * corner should go, relative to the base image behind it.
         */
        int xPos = 0;
        int yPos = 0;

        switch (overlayedImagePosition)
        {
            case TOP_LEFT:
                break;

            case TOP_RIGHT:
                xPos = baseImageData.width - overlayedImageData.width;
                break;

            case BOTTOM_LEFT:
                yPos = baseImageData.height - overlayedImageData.height;
                break;

            case BOTTOM_RIGHT:
                xPos = baseImageData.width - overlayedImageData.width;
                yPos = baseImageData.height - overlayedImageData.height;
                break;

            case CENTER:
                xPos = (baseImageData.width - overlayedImageData.width) / 2;
                yPos = (baseImageData.height - overlayedImageData.height) / 2;
                break;

            default:
                break;
        }

        drawImage(baseImageData, 0, 0);
        drawImage(overlayedImageData, xPos, yPos);
    }


    // ==================== 7. Getters & Setters ====================

    final public ImageOverlayer overlayImagePosition(final OverlayedImagePosition overlayImagePosition)
    {
        this.overlayedImagePosition = overlayImagePosition;
        return this;
    }


    final public ImageOverlayer baseImage(final ImageData baseImageData)
    {
        this.baseImageData = baseImageData;
        return this;
    }


    final public ImageOverlayer baseImage(final Image baseImage)
    {
        this.baseImageData = baseImage.getImageData();
        return this;
    }


    final public ImageOverlayer baseImage(final ImageDescriptor baseImageDescriptor)
    {
        this.baseImageData = baseImageDescriptor.getImageData();
        return this;
    }


    final public ImageOverlayer overlayImage(final ImageData overlayImageData)
    {
        this.overlayedImageData = overlayImageData;
        return this;
    }


    final public ImageOverlayer overlayImage(final Image overlayImage)
    {
        this.overlayedImageData = overlayImage.getImageData();
        return this;
    }


    final public ImageOverlayer overlayImage(final ImageDescriptor overlayImageDescriptor)
    {
        this.overlayedImageData = overlayImageDescriptor.getImageData();
        return this;
    }


    @Override
    protected Point getSize()
    {
        // The size of the composite image is determined by the maximum size between the two building images,
        // although keep in mind that the base image always comes underneath the overlaying one.
        return new Point( max(baseImageData.width, overlayedImageData.width), max(baseImageData.height, overlayedImageData.height) );
    }

}
Krimmer answered 6/3, 2015 at 9:18 Comment(2)
this piece greatly helped me. However I see some white pixels at the border (See link) . I am not sure how to get rid of them. Can you please help ? i.imgur.com/nz7fwsH.png?1Dorran
@Dorran Please make sure gc.setAntialias(SWT.ON); is there. This seems like an aliasing problem to me.Krimmer
A
2

Also you can use control decorations for this. The advantage is you can easily hide/show the notification with hide() and show() methods and add tool-tip text and listeners to it.

Check this blog for how to use control decoration. Use Button widget instead of Text for your case.

Create the notification image as shown below and set it to ControlDecoration object.

    Image image = new Image(display, 20, 25);
    GC gc = new GC(image);
    gc.setBackground(display.getSystemColor(SWT.COLOR_RED));
    gc.fillRectangle(0, 0, 20, 25);
    gc.setForeground(display.getSystemColor(SWT.COLOR_WHITE));
    int notif = 5;
    gc.drawText(new Integer(notif).toString(), 5, 5);
    gc.dispose();
Angelesangelfish answered 6/3, 2015 at 12:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.