/******************************************************************************* * Copyright (c) 2014 Mentor Graphics and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Mentor Graphics - initial API and implementation *******************************************************************************/ package com.codesourcery.internal.installer.ui; import java.util.Timer; import java.util.TimerTask; import org.eclipse.swt.SWT; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.events.PaintListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Canvas; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; /** * This control shows a spinning progress animation * and optional text. * Call <code>setProgress</code> to start and stop the progress * animation. */ public class SpinnerProgress extends Canvas { /** Text drawing flags */ private final int TEXT_FLAGS = SWT.DRAW_TRANSPARENT | SWT.DRAW_MNEMONIC; /** Default number of animation spokes */ private final int DEFAULT_NUMBER_OF_SPOKES = 8; /** Default width */ private final int DEFAULT_WIDTH = 16; /** Default height */ private final int DEFAULT_HEIGHT = 16; /** Animation delay */ private final int ANIMATION_DELAY = 100; /** Margin */ private final int MARGIN = 0; /** Margin between animation and text */ private final int TEXT_MARGIN = 4; /** Colors */ private Color[] colors; /** Computed spoke angles */ private double[] spokeAngles; /** Animate progress */ private int animateProgress = 0; /** Animation timer */ private Timer animationTimer; /** Text */ private String text; /** Number of spokes */ private int numberOfSpokes = DEFAULT_NUMBER_OF_SPOKES; /** * Constructor * * @param parent Parent * @param style Style flags */ public SpinnerProgress(Composite parent, int style) { super(parent, style); // Add paint listener addPaintListener(new PaintListener() { @Override public void paintControl(PaintEvent e) { onPaint(e); } }); // Set font setFont(parent.getFont()); // Create spoke colors createColors(getDisplay()); // Compute spoke angles computeSpokeAngles(); } /** * Constructor * * @param parent Parent * @param style Style flags * @param numberOfSpokes Number of spokes in animation */ public SpinnerProgress(Composite parent, int style, int numberOfSpokes) { this(parent, style); this.numberOfSpokes = numberOfSpokes; } /** * Starts/stops progress animation. * * @param enable <code>true</code> to enable */ public void setProgress(boolean enable) { if (enable) { animationTimer = new Timer(true); animationTimer.schedule(new TimerTask() { @Override public void run() { onTimer(); } }, 0, ANIMATION_DELAY); } else { if (animationTimer != null) { animationTimer.cancel(); animationTimer = null; redraw(); } } } @Override public void dispose() { setProgress(false); if (colors != null) { for (Color color : colors) { color.dispose(); } } super.dispose(); } /** * Sets the text to display. * * @param text Text */ public void setText(String text) { this.text = text; if (!isDisposed()) { redraw(); } } /** * Returns the text. * * @return Text */ public String getText() { return text; } /** * Called on the animation timer. */ private void onTimer() { if (!isDisposed()) { getDisplay().syncExec(new Runnable() { @Override public void run() { if (!isDisposed()) { if (isEnabled()) { animateProgress = ++animateProgress % numberOfSpokes; redraw(); } } } }); } } @Override public void setForeground(Color c) { super.setForeground(c); if (colors != null) { for (Color color : colors) { color.dispose(); } } createColors(getDisplay()); } /** * Creates the colors for the spoke animation. * * @param display Display */ private void createColors(Display display) { colors = new Color[numberOfSpokes]; byte bytIncrement = (byte)(Byte.MAX_VALUE / numberOfSpokes); Color foreground = getForeground(); Color background = getBackground(); byte PERCENTAGE_OF_DARKEN = 0; for (int intCursor = 0; intCursor < numberOfSpokes; intCursor++) { if (intCursor == 0) colors[intCursor] = foreground; else { PERCENTAGE_OF_DARKEN += bytIncrement; if (PERCENTAGE_OF_DARKEN > Byte.MAX_VALUE) PERCENTAGE_OF_DARKEN = Byte.MAX_VALUE; RGB rgb = blendRGB(foreground.getRGB(), background.getRGB(), PERCENTAGE_OF_DARKEN); colors[intCursor] = new Color(display, rgb); } } } /** * Blends two RGB values using the provided ratio. * * @param c1 First RGB value * @param c2 Second RGB value * @param ratio Percentage of the first RGB to blend with * second RGB (0-100) * * @return The RGB value of the blended color */ public static RGB blendRGB(RGB c1, RGB c2, int ratio) { ratio = Math.max(0, Math.min(255, ratio)); int r = Math.max(0, Math.min(255, (ratio * c1.red + (100 - ratio) * c2.red) / 100)); int g = Math.max(0, Math.min(255, (ratio * c1.green + (100 - ratio) * c2.green) / 100)); int b = Math.max(0, Math.min(255, (ratio * c1.blue + (100 - ratio) * c2.blue) / 100)); return new RGB(r, g, b); } @Override public Point computeSize(int wHint, int hHint, boolean changed) { return computeSize(wHint, hHint); } @Override public Point computeSize(int wHint, int hHint) { Point textSize = getTextSize(getText()); int minHeight; if (hHint == SWT.DEFAULT) { minHeight = MARGIN + MARGIN + Math.max(DEFAULT_HEIGHT, textSize.y); } else { minHeight = hHint; } int minWidth = Math.max(wHint, MARGIN + MARGIN + TEXT_MARGIN + DEFAULT_WIDTH + textSize.x); return new Point(minWidth, minHeight); } /** * Computes text size. * * @param text Text to compute * @return Text size */ private Point getTextSize(String text) { Point textSize; if (text == null) { textSize = new Point(0, 0); } else { GC gc = new GC(getShell()); gc.setFont(getFont()); textSize = gc.textExtent(getText(), TEXT_FLAGS); gc.dispose(); } return textSize; } /** * Computes the animation spoke angles. */ private void computeSpokeAngles() { spokeAngles = new double[numberOfSpokes]; double dblAngle = (double)360 / numberOfSpokes; for (int shtCounter = 0; shtCounter < numberOfSpokes; shtCounter++) spokeAngles[shtCounter] = (shtCounter == 0 ? dblAngle : spokeAngles[shtCounter - 1] + dblAngle); } /** * Called to paint the control. * |----------------------------------------------| * |{MARGIN}{Animation}{TEXT_MARGIN}{Text}{MARGIN}| * |----------------------------------------------| * * @param event Paint event */ private void onPaint(PaintEvent event) { // Get the client area Rectangle clientArea = getClientArea(); // Get the graphics context GC gc = event.gc; // Set the font gc.setFont(getFont()); // Color gc.setForeground(getForeground()); int max = Math.min((clientArea.width - MARGIN) / 2, (clientArea.height - MARGIN) / 2); int outerRadius = max; int innerRadius = max / 2; int spokeThickness = max / 4; if (animationTimer != null) { Point center = new Point(clientArea.x + max + MARGIN, clientArea.y + max + MARGIN); int position = animateProgress; for (int counter = 0; counter < numberOfSpokes; counter++) { position = position % numberOfSpokes; gc.setLineWidth(spokeThickness); gc.setForeground(colors[counter]); Point startPoint = GetCoordinate(center, innerRadius, spokeAngles[position]); Point endPoint = GetCoordinate(center, outerRadius, spokeAngles[position]); gc.drawLine(startPoint.x, startPoint.y, endPoint.x, endPoint.y); position++; } } // Draw text String text = getText(); if (text != null) { gc.setForeground(colors[numberOfSpokes / 2]); gc.drawText(text, max + max + TEXT_MARGIN + MARGIN, MARGIN, TEXT_FLAGS); } } /** * Returns the coordinates for a point * at a given radius and angle from a center * coordinate. * * @param center Center coordinate * @param radius Radius * @param angle Angle * @return Coordinate */ private Point GetCoordinate(Point center, int radius, double angle) { double ang = Math.PI * angle / 180; return new Point(center.x + (int)(radius * (float)Math.cos(ang)), (int)(center.y + radius * (float)Math.sin(ang))); } }