All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.tinymediamanager.ui.images.LoadingSpinner Maven / Gradle / Ivy

The newest version!
package org.tinymediamanager.ui.images;

import java.awt.Color;
import java.awt.Component;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.Point2D;
import java.awt.geom.RoundRectangle2D;

import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.Timer;

/**
 * The class LoadingSpinner is used to display a loading spinner.
 *
 * @author Manuel Laggner Created by laggner on 04.04.2016.
 */
public class LoadingSpinner implements Icon {
  private int        width;
  private int        height;
  private Area[]     ticker;
  private Area[]     shadow;
  private Timer      timer;
  private JComponent parent;
  private int        barsCount       = 14;

  private Color      baseColorLow    = new Color(80, 80, 80);
  private Color      baseColorHigh   = new Color(120, 120, 120);

  private Color      customColorLow  = null;
  private Color      customColorHigh = null;

  private boolean    active          = false;

  public LoadingSpinner(int size, JComponent parent) {
    this.width = this.height = size;
    this.parent = parent;
    ticker = buildTicker();
    shadow = buildShadow();

    int fps = 10;
    timer = new Timer(1000 / fps, new TimerTickActionListener());
  }

  public void setCustomColors(Color low, Color high) {
    customColorLow = low;
    customColorHigh = high;
    parent.repaint();
  }

  public void resetCustomColor() {
    customColorHigh = customColorLow = null;
    parent.repaint();
  }

  @Override
  public void paintIcon(Component c, Graphics g, int x, int y) {
    Graphics2D g2 = (Graphics2D) g.create();
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

    // create offset to match the parents align
    g2.translate(x, y);

    g2.setColor(new Color(255, 255, 255, 0));
    g2.fillRect(0, 0, width - 1, height - 1);

    // active = rotating circle
    if (active) {
      // draw shadow
      for (int i = 0; i < shadow.length; i++) {
        int alpha = (int) (150 * (1 - i / (shadow.length * 2.0f / 3.0f)));
        if (alpha < 0) {
          alpha = 0;
        }
        Color shadowColor = new Color(0, 0, 0, alpha);
        g2.setColor(shadowColor);
        g2.fill(shadow[i]);
      }

      // draw spinner
      for (int i = 0; i < ticker.length; i++) {
        float factor = (1 - 1 / 2.0f * i / (float) (ticker.length - 1));

        int alpha = (int) (255 * (1 - i / (ticker.length * 2.0f / 3.0f)));
        if (alpha < 0) {
          alpha = 0;
        }
        Color[] colors = calculateColors(factor, alpha);
        g2.setPaint(new GradientPaint(width / 2, 0, colors[1], width / 2, height, colors[0]));
        // g2.setColor(new Color(color, color, color, alpha));
        g2.fill(ticker[i]);
      }
    }
    else {
      // draw shadow
      Color shadowColor = new Color(0, 0, 0, 150);
      g2.setColor(shadowColor);
        for (Area aShadow : shadow) {
            g2.fill(aShadow);
        }

      // draw spinner
        for (Area aTicker : ticker) {
            Color[] colors = calculateColors(1, 255);
            g2.setPaint(new GradientPaint(width / 2, 0, colors[1], width / 2, height, colors[0]));
            g2.fill(aTicker);
        }
    }
  }

  private Color[] calculateColors(float factor, int alpha) {
    Color[] colors = new Color[2];
    Color low;
    Color high;

    if (factor == 1.0f && alpha == 255) {
      // no need for calculating a color
      if (customColorLow != null && customColorHigh != null) {
        low = customColorLow;
        high = customColorHigh;
      }
      else {
        low = baseColorLow;
        high = baseColorHigh;
      }
    }
    else {
      // re-calculate the color
      if (customColorLow != null && customColorHigh != null) {
        low = new Color((int) (customColorLow.getRed() * factor), (int) (customColorLow.getGreen() * factor),
            (int) (customColorLow.getBlue() * factor), alpha);
        high = new Color((int) (customColorHigh.getRed() * factor), (int) (customColorHigh.getGreen() * factor),
            (int) (customColorHigh.getBlue() * factor), alpha);
      }
      else {
        low = new Color((int) (baseColorLow.getRed() * factor), (int) (baseColorLow.getGreen() * factor), (int) (baseColorLow.getBlue() * factor),
            alpha);
        high = new Color((int) (baseColorHigh.getRed() * factor), (int) (baseColorHigh.getGreen() * factor), (int) (baseColorHigh.getBlue() * factor),
            alpha);
      }
    }

    colors[0] = low;
    colors[1] = high;
    return colors;
  }

  @Override
  public int getIconWidth() {
    return width;
  }

  @Override
  public int getIconHeight() {
    return height;
  }

  public void start() {
    if (!active) {
      active = true;
      timer.start();
    }
  }

  public void stop() {
    if (active) {
      active = false;
      timer.stop();
    }
    parent.repaint();
  }

  private Area[] buildTicker() {
    Area[] ticker = new Area[barsCount];
    Point2D.Double center = new Point2D.Double((double) width / 2, (double) (height / 2));
    double fixedAngle = 2.0 * Math.PI / ((double) barsCount);

    for (double i = 0.0; i < (double) barsCount; i++) {
      Area primitive = new Area(new RoundRectangle2D.Double(0, 0, width / 4, height / 15, height / 15, height / 15));

      AffineTransform toCenter = AffineTransform.getTranslateInstance(center.getX(), center.getY());
      AffineTransform toBorder = AffineTransform.getTranslateInstance(width / 10, -height / 15);
      AffineTransform toCircle = AffineTransform.getRotateInstance(-i * fixedAngle, center.getX(), center.getY());

      AffineTransform toWheel = new AffineTransform();
      toWheel.concatenate(toCenter);
      toWheel.concatenate(toBorder);

      primitive.transform(toWheel);
      primitive.transform(toCircle);

      ticker[(int) i] = primitive;
    }

    return ticker;
  }

  private Area[] buildShadow() {
    Area[] shadow = new Area[barsCount];
    Point2D.Double center = new Point2D.Double((double) width / 2, (double) (height / 2));
    double fixedAngle = 2.0 * Math.PI / ((double) barsCount);

    for (double i = 0.0; i < (double) barsCount; i++) {
      Area primitive = new Area(new RoundRectangle2D.Double(0, 0, width / 4, height / 15, height / 15, height / 15));

      AffineTransform toCenter = AffineTransform.getTranslateInstance(center.getX(), center.getY() + 1);
      AffineTransform toBorder = AffineTransform.getTranslateInstance(width / 10, -height / 15);
      AffineTransform toCircle = AffineTransform.getRotateInstance(-i * fixedAngle, center.getX(), center.getY() + 1);

      AffineTransform toWheel = new AffineTransform();
      toWheel.concatenate(toCenter);
      toWheel.concatenate(toBorder);

      primitive.transform(toWheel);
      primitive.transform(toCircle);

      shadow[(int) i] = primitive;
    }

    return shadow;
  }

  /*
   * this class is used to react on the timer ticks
   */
  private class TimerTickActionListener implements ActionListener {
    @Override
    public void actionPerformed(ActionEvent e) {
      // Rotate the spinner by one tick
      Point2D.Double center = new Point2D.Double((double) width / 2, (double) height / 2);
      double fixedIncrement = 2.0 * Math.PI / ((double) barsCount);
      AffineTransform toCircle = AffineTransform.getRotateInstance(fixedIncrement, center.getX(), center.getY());

        for (Area aTicker : ticker) {
            aTicker.transform(toCircle);
        }

      // rotate the shadow by one tick
      toCircle = AffineTransform.getRotateInstance(fixedIncrement, center.getX(), center.getY() + 1);
        for (Area aShadow : shadow) {
            aShadow.transform(toCircle);
        }

      parent.repaint();
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy