Painter.java

package swingtree.api;


import swingtree.UI;
import swingtree.animation.Animation;
import swingtree.animation.AnimationState;

import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.geom.RoundRectangle2D;
import java.util.concurrent.TimeUnit;

/**
 *  A functional interface for doing custom painting on a component
 *  using the {@link Graphics2D} API.
 *  This is typically used to paint <b>custom styles on components</b> as part of the style API
 *  exposed by {@link swingtree.UIForAnySwing#withStyle(Styler)}, like so:
 *  <pre>{@code
 *  UI.label("I am a label")
 *  .withStyle( it -> it
 *    .size(120, 50)
 *    .padding(6)
 *    .painter(UI.Layer.BACKGROUND, g -> {
 *      g.setColor(Color.ORANGE);
 *      var e = new Ellipse2D.Double(5,5,25,25);
 *      g.fill(UI.scale(e);
 *    })
 *    .fontSize(12)
 *  )
 *  }</pre>
 *  Which is based on the {@link swingtree.style.ComponentStyleDelegate#painter(UI.Layer, Painter)}.
 *  You may also want to take a look at <br>
 *  {@link swingtree.style.ComponentStyleDelegate#painter(UI.Layer, UI.ComponentArea, Painter)}, <br>
 *  {@link swingtree.style.ComponentStyleDelegate#painter(UI.Layer, String, Painter)} and <br>
 *  {@link swingtree.style.ComponentStyleDelegate#painter(UI.Layer, UI.ComponentArea, String, Painter)}. <br>
 *  <br>
 *  You can also use painter implementations
 *  for <b>defining custom component event based animations</b> by registering through the
 *  {@link swingtree.ComponentDelegate#paint(AnimationState, Painter)} method
 *  inside of an {@link Animation} registered through
 *  {@link swingtree.ComponentDelegate#animateFor(double, TimeUnit, Animation)}.
 *  <br><br>
 *  Note that inside the painter the {@link Graphics2D} context may not
 *  be scaled according to the current UI scale factor (for high DPI displays). <br>
 *  Check out the following methods for scaling your paint operations: <br>
 *  <ul>
 *      <li>{@link UI#scale()} - returns the current UI scale factor.</li>
 *      <li>{@link UI#scale(Graphics2D)} - scales the given graphics context according to the current UI scale factor.</li>
 *      <li>{@link UI#scale(double)} - scales the given value according to the current UI scale factor.</li>
 *      <li>{@link UI#scale(float)} - scales the given value according to the current UI scale factor.</li>
 *      <li>{@link UI#scale(int)} - scales the given value according to the current UI scale factor.</li>
 *      <li>{@link UI#scale(Insets)} - scales the given insets according to the current UI scale factor.</li>
 *      <li>{@link UI#scale(Dimension)} - scales the given dimension according to the current UI scale factor.</li>
 *      <li>{@link UI#scale(Rectangle)} - scales the given rectangle according to the current UI scale factor.</li>
 *      <li>{@link UI#scale(RoundRectangle2D)} - scales the given round rectangle according to the current UI scale factor.</li>
 *      <li>{@link UI#scale(java.awt.geom.Ellipse2D)} - scales the given ellipse according to the current UI scale factor.</li>
 *  </ul><br>
 *  <br>
 *  Note that your custom painters will yield the best performance if they are stateless and immutable
 *  as well has if they have a good {@link Object#hashCode()} and {@link Object#equals(Object)} implementation.
 *  This is because it allows SwingTree to cache the rendering of the painters and avoid unnecessary repaints. <br>
 *  <b>If you do not want to create a custom class just for painting but instead
 *  just want to pass an immutable cache key to a painter, then consider using the
 *  {@link #of(Object, Painter)} factory method to create a painter that has the
 *  with {@link Object#hashCode()} and {@link Object#equals(Object)} implemented as a delegate to the data object.</b>
 *  <p>
 *  <b>Also consider taking a look at the <br> <a href="https://globaltcad.github.io/swing-tree/">living swing-tree documentation</a>
 *  where you can browse a large collection of examples demonstrating how to use the API of Swing-Tree in general.</b>
 *
 */
@FunctionalInterface
public interface Painter
{
    /**
     *  Exposes a constant painter that paints nothing.
     *  This is useful as a no-op null object pattern.
     * @return A painter that paints nothing.
     */
    static Painter none() { return Constants.PAINTER_NONE; }

    /**
     *  Allows you to create a cacheable painter that uses the given data object as a cache key.
     *  The provided data object should be immutable and have exhaustive {@link Object#hashCode()}
     *  and {@link Object#equals(Object)} implementations. <br>
     *  The data object is expected to be the sole source of information for the painter's painting operations.
     *  Otherwise, the usage of this method is discouraged as cache based
     *  rendering may not reflect the actual state of the component. <br>
     *
     * @param data The data object to use as a cache key, must be immutable and have
     *             exhaustive {@link Object#hashCode()} and {@link Object#equals(Object)} implementations.
     * @param painter The painter to use for painting, must be stateless and immutable as well.
     *                It is expected to use the given data object as the sole source of information
     *                for its painting operations.
     * @return A cache friendly painter that uses the given data object as a cache key.
     * @param <D> The type of the data object to use as a cache key.
     */
    static <D> Painter of( D data, Painter painter ) {
        return new CachablePainter(data, painter);
    }

    /**
     * Paints a custom style on a component using the given graphics context.
     * @param g2d the graphics context to use for painting.
     */
    void paint( Graphics2D g2d );

    /**
     *  If a painter implementation reports that it can be cached, SwingTree will
     *  use the painter as a cache key and the result of its painting operations
     *  will be cached and reused for equal cache keys. <br>
     *  So a painter that can be cached should be stateless and immutable as well as
     *  have exhaustive {@link Object#hashCode()} and
     *  {@link Object#equals(Object)} implementations. <br>
     *
     * @return true If the painting operation is cachable, false otherwise.
     */
    default boolean canBeCached() { return false; }

    /**
     * Returns a new painter that paints this painter's style and then the given painter's style.
     * @param after the painter to paint after this painter.
     * @return a new painter that paints this painter's style and then the given painter's style.
     */
    default Painter andThen( Painter after ) {
        return new AndThenPainter(this, after);
    }

}