StyleInstaller.java

package swingtree.style;

import net.miginfocom.layout.CC;
import net.miginfocom.layout.ConstraintParser;
import net.miginfocom.layout.DimConstraint;
import net.miginfocom.layout.UnitValue;
import net.miginfocom.swing.MigLayout;
import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import swingtree.SwingTree;
import swingtree.UI;
import swingtree.api.Configurator;
import swingtree.api.Painter;
import swingtree.components.JIcon;
import swingtree.layout.Bounds;

import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.text.JTextComponent;
import java.awt.*;
import java.lang.reflect.Modifier;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Predicate;

/**
 *  This contains all the logic needed for installing the SwingTree style
 *  configurations on a particular component reasonably gracefully.
 *  SwingTree adds a lot of features on top of
 *  regular AWT/Swing. <br>
 *  But it turns out, this is actually fairly difficult,
 *  because when it comes to rendering the UI, Swing has an all or nothing approach:
 *  Either completely reinstall the look and feel of a component ({@link javax.swing.plaf.ComponentUI}),
 *  or you leave it be. Anything in between is a finicky situation which
 *  is exactly where SwingTree is situated. <br>
 *  The core problem here is that the transition between a SwingTree style and the look and feel
 *  and border of a raw Swing component should be as smooth as possible.
 *  If the user defines a border radius for a component, then this should
 *  not automatically lead to its look and feel being lost.
 *  Instead, only parts of the original look and feel should be applied.
 *  This is also true for other component properties like the background color,
 *  the foreground color, the font and the opacity flag.<br>
 *  This last part is especially tricky, because the opacity flag is a very
 *  important property for the performance of Swing components.
 *  <p>
 *  So this class orchestrates under which precise condition, and how, the component should be mutated to
 *  enable the SwingTree style to be effective. <br>
 *  Naturally, there are various workarounds and hacks to make this work.
 *  The most noteworthy is that when a component is styled to have things rendered in the background layer,
 *  such as a gradient or a shadow. In that case SwingTree needs to paint this stuff
 *  before the Look and Feel renders the contents of the component.
 * <p>
 *  This is not a problem by itself, especially for naturally non-opaque components like {@code JLabel} or
 *  {@link swingtree.components.JBox}, but for opaque components like {@code JPanel} most Look and Feel's
 *  {@link javax.swing.plaf.ComponentUI}s will always fill out the entire background with the AWT background
 *  color taken from {@code Component.getBackground()}.<br>
 *  This is a problem because it will cover up any custom painting done in the background layer of the style!<br>
 * <p>
 *  Now, to overcome this problem, this class implements a special "background color bypass" mechanism that it
 *  applies to opaque components that have background layer styles.
 *  It takes the {@code Component.getBackground()} and sets it in the SwingTree style,
 *  and then sets the AWT background color to {@code UI.Color.UNDEFINED}, which is transparent.
 *  That way SwingTree will effectively take over the responsibility of painting the component background!
 *
 * @param <C> The type of the component.
 */
final class StyleInstaller<C extends JComponent>
{
    private static final Logger log = LoggerFactory.getLogger(StyleInstaller.class);

    private DynamicLaF        _dynamicLaF = DynamicLaF.none(); // Not null, but can be DynamicLaF.none().
    private @Nullable Color   _overriddenBackgroundColor = null;
    private @Nullable Color   _outSideBackgroundColor    = null;
    private @Nullable Color   _lastInsideBackgroundColor = null;
    private @Nullable Boolean _initialIsOpaque           = null;
    private @Nullable Boolean _initialContentAreaFilled  = null;
    private @Nullable Font    _initialFont               = null;
    private @Nullable Color   _initialForeground         = null; // the foreground from before the style set one, UI.Color.UNDEFINED when the component had none of its own (see _installForeground)
    private @Nullable Color   _installedForeground       = null; // the foreground the style set last, UI.Color.UNDEFINED for none, so that only that one is taken back (see _restoreForeground)
    private @Nullable Color   _initialViewportBackground = null; // set when the styled background is handed down to a scroll pane viewport (see _restoreViewportBackgroundOf)
    private @Nullable Color   _handedDownViewportBackground = null; // the color the style engine gave the viewport, so it only takes that one back (see _restoreViewportBackgroundOf)
    private @Nullable Boolean _initialViewportOpaque     = null; // set when the opaqueness is taken from a scroll pane viewport (see _updateViewportOpaquenessOf)
    // Remember the component's minimum/maximum size from before the style engine overrode them, so
    // that they can be restored once a (possibly animated/transitional) style stops specifying them.
    // A 'true' ownership flag means the style engine currently holds an explicit override;
    // the matching '_initial*' field holds the value to restore (null == "was not explicitly set").
    // Note: we deliberately do NOT do this for the *preferred* size. The preferred size is only a
    // hint (it cannot pin a component the way a minimum can), and it is also driven by the auto
    // preferred height feature (see TextConf#autoPreferredHeight), which expects the last computed
    // value to stick when it is switched off, rather than snap back to a natural size.
    private boolean            _styleOwnsMinSize = false;
    private boolean            _styleOwnsMaxSize = false;
    private @Nullable Dimension _initialMinSize  = null;
    private @Nullable Dimension _initialMaxSize  = null;

    void updateDynamicLookAndFeel(Configurator<DynamicLaF> updater) {
        try {
            _dynamicLaF = updater.configure(_dynamicLaF);
        } catch (Exception e) {
            log.error(SwingTree.get().logMarker(), "Failed to update dynamic look and feel!", e);
        }
    }

    void installCustomBorderBasedStyleAndAnimationRenderer( C owner, StyleConf styleConf) {
        Border currentBorder = owner.getBorder();
        if ( !(currentBorder instanceof StyleAndAnimationBorder) )
            owner.setBorder(new StyleAndAnimationBorder<>(ComponentBackend.powering(owner), currentBorder, styleConf));
    }

    StyleConf recalculateInsets( C owner, StyleConf styleConf ) {
        if ( owner.getBorder() instanceof StyleAndAnimationBorder ) {
            final OptionalInsets paddingCorrection = _formerBorderPaddingCorrection(owner, styleConf);
            final OptionalInsets adjustedPadding   = styleConf.border().padding().or(paddingCorrection);
            styleConf = styleConf._withBorder(styleConf.border().withPadding(adjustedPadding));
            StyleAndAnimationBorder<?> border = (StyleAndAnimationBorder<?>) owner.getBorder();
            border.recalculateInsets(styleConf);
        }
        return styleConf;
    }

    void installCustomUIFor( C owner ) {
        _dynamicLaF.installCustomUIFor(owner);
    }

    boolean customLookAndFeelIsInstalled(C owner) {
        return _dynamicLaF.customLookAndFeelIsInstalled(owner) || _dynamicLaF.currentLookAndFeelSupportsSwingTree(owner);
    }

    static OptionalInsets _formerBorderMarginCorrection(JComponent owner) {
        Border border = owner.getBorder();
        if ( border instanceof StyleAndAnimationBorder ) {
            return ((StyleAndAnimationBorder<?>) border).getDelegatedInsetsComponentAreaCorrection();
        }
        return OptionalInsets.none();
    }

    OptionalInsets _formerBorderPaddingCorrection( C owner, StyleConf conf ) {
        Border border = owner.getBorder();
        OptionalInsets result = OptionalInsets.none();
        if ( border instanceof StyleAndAnimationBorder ) {
            result = ((StyleAndAnimationBorder<?>) border).getDelegatedInsets(conf);
        }
        return result.map( v -> v <= 0 ? null : v );
    }

    StyleEngine _updateEngine(
        final C           owner,
        final StyleEngine engine,
        final StyleConf   newStyle
    ) {
        /*
            HTML scaling / FontConf injection must run every paint cycle.
            JLabel uses the standard settle-check (works because BasicHTML
            does NOT re-normalise the document model on round-trip).
            JEditorPane uses a settle-guard that extracts our injected CSS
            block rather than comparing whole strings — HTMLEditorKit
            normalises whitespace/comments/structure so string equality
            perpetually fails and would cause an infinite setText loop.
        */
        if ( owner instanceof JLabel ) {
            LabelStyleInstallerUtility._applyHtmlScalingAndStyle(
                (JLabel) owner, newStyle.font()
            );
        } else if ( owner instanceof JEditorPane ) {
            LabelStyleInstallerUtility.htmlSettles(
                (JEditorPane) owner, newStyle.font()
            );
        }

        StyleConf adjustedStyle = newStyle;
        if ( StyleUtil.isUndefinedColor(owner.getBackground()) ) {
            if (owner.isOpaque() && _overriddenBackgroundColor != null && !adjustedStyle.base().backgroundColor().isPresent())
                adjustedStyle = adjustedStyle.backgroundColor(_overriddenBackgroundColor);
        }
        _lastInsideBackgroundColor = owner.getBackground();
        return engine.update(
                Bounds.of(owner.getX(), owner.getY(), owner.getWidth(), owner.getHeight()),
                adjustedStyle,
                _formerBorderMarginCorrection(owner)
            );
    }

    StyleEngine applyStyleToComponentState(
        final C              owner, // <- The component we want to style.
        final StyleEngine    engine,
        final StyleSource<C> styleSource,
        StyleConf            newStyle,
        final boolean        force
    ) {
        boolean initialIsOpaqueFlagState = owner.isOpaque();
        boolean initialIsContentAreaFilled = ( owner instanceof AbstractButton && ((AbstractButton) owner).isContentAreaFilled() );
        Runnable backgroundSetter = ()->{
            if ( StyleUtil.isUndefinedColor(owner.getBackground()) )
                _establishDefaultBackgroundColorFor(owner);
            /*
                The default background setter ensures that the background
                cannot be in an undefined state, we use the identity of the
                UI.Color.UNDEFINED constant to check this.

                Note that the undefined background is a state
                unique to a certain style state where we need to override
                the native look and feel...
            */
        };

        boolean doInstallation = true;
        boolean backgroundWasSetSomewhereElse = this.backgroundWasChangedSomewhereElse(owner);
        if ( backgroundWasSetSomewhereElse ) {
            _outSideBackgroundColor = owner.getBackground();
            Color outSideBackgroundColor = _outSideBackgroundColor;
            backgroundSetter = () -> {
                if ( !Objects.equals( owner.getBackground(), outSideBackgroundColor ) )
                    owner.setBackground(outSideBackgroundColor);
            };
        }

        StyleConf oldStyle = engine.getComponentConf().style();
        if ( !force ) {
            // We check if it makes sense to apply the new style:
            if ( !backgroundWasSetSomewhereElse && oldStyle.equals(newStyle) )
                doInstallation = false;
        }

        if ( !doInstallation ) {
            final OptionalInsets paddingCorrection = _formerBorderPaddingCorrection(owner, newStyle);
            final OptionalInsets adjustedPadding   = newStyle.border().padding().or(paddingCorrection);
            newStyle = newStyle._withBorder(newStyle.border().withPadding(adjustedPadding));

            if ( owner.getBorder() instanceof StyleAndAnimationBorder<?> ) {
                StyleAndAnimationBorder<C> border = (StyleAndAnimationBorder<C>) owner.getBorder();
                border.recalculateInsets(newStyle);
            }

            return _updateEngine(owner, engine, newStyle);
        }

        final boolean isSwingTreeComponent = owner instanceof StylableComponent;

        final boolean isStyled            = !newStyle.equals(StyleConf.none());
        final boolean hasPaddingAndMargin = isStyled && !StyleConf.none().hasEqualMarginAndPaddingAs(newStyle);
        final boolean hasBorderStyle      = isStyled && !StyleConf.none().hasEqualBorderAs(newStyle);
        final boolean hasBaseStyle        = isStyled && !StyleConf.none().hasEqualBaseAs(newStyle);
        final boolean hasBaseColors       = isStyled && (hasBaseStyle && newStyle.base().hasAnyColors());
        final boolean hasBackFilter       = isStyled && !FilterConf.none().equals(newStyle.layers().filter());

        final boolean weNeedToInstallTheCustomBorder = isStyled && (
               hasPaddingAndMargin || hasBorderStyle
               || newStyle.layers().any( (layer, it) -> layer.isOneOf(UI.Layer.BORDER, UI.Layer.CONTENT) && it.shadows().any(named -> named.style().color().isPresent() ) )
               || newStyle.layers().any( (layer, it) -> layer.isOneOf(UI.Layer.BORDER, UI.Layer.CONTENT) && it.gradients().any(named -> named.style().colors().length > 0 ) )
               || newStyle.layers().any( (layer, it) -> layer.isOneOf(UI.Layer.BORDER, UI.Layer.CONTENT) && it.images().any(named -> named.style().image().isPresent() || named.style().primer().isPresent() ) )
               || newStyle.layers().any( (layer, it) -> layer.isOneOf(UI.Layer.BORDER, UI.Layer.CONTENT) && it.texts().any(named -> !TextConf.none().equals(named.style()) ) )
               || newStyle.layers().any( (layer, it) -> layer.isOneOf(UI.Layer.BORDER, UI.Layer.CONTENT) && it.painters().any(named -> !Painter.none().equals(named.style().painter()) ) )
               || newStyle.layers().any( (layer, it) -> layer.isOneOf(UI.Layer.BORDER, UI.Layer.CONTENT) && it.noises().any(named -> named.style().get().colors().length > 0 ) )
            );

        final boolean weNeedToInstallTheCustomUI = isStyled && (
               (hasBackFilter && !isSwingTreeComponent) ||
               (hasBaseColors && newStyle.base().requiresCustomUI())
               || newStyle.layers().any( (layer, it) -> layer == UI.Layer.BACKGROUND && it.shadows().any(named -> named.style().color().isPresent() ) )
               || newStyle.layers().any( (layer, it) -> layer == UI.Layer.BACKGROUND && it.gradients().any(named -> named.style().colors().length > 0 ) )
               || newStyle.layers().any( (layer, it) -> layer == UI.Layer.BACKGROUND && it.images().any(named -> named.style().image().isPresent() || named.style().primer().isPresent() ) )
               || newStyle.layers().any( (layer, it) -> layer == UI.Layer.BACKGROUND && it.painters().any(named -> !Painter.none().equals(named.style().painter()) ) )
               || newStyle.layers().any( (layer, it) -> layer == UI.Layer.BACKGROUND && it.texts().any(named -> !TextConf.none().equals(named.style()) ) )
               || newStyle.layers().any( (layer, it) -> layer == UI.Layer.BACKGROUND && it.noises().any(named -> named.style().get().colors().length > 0 ) )
            );

        if ( weNeedToInstallTheCustomBorder ) {
            installCustomBorderBasedStyleAndAnimationRenderer(owner, newStyle);
            newStyle = recalculateInsets(owner, newStyle);
        } else if ( styleSource.hasNoAnimationStylers() ) {
            _uninstallCustomBorderBasedStyleAndAnimationRenderer(owner);
        }

        if ( weNeedToInstallTheCustomUI ) {
            _dynamicLaF = _dynamicLaF.establishLookAndFeelFor(newStyle, owner);
        } else {
            if ( _outSideBackgroundColor != null ) {
                if ( !Objects.equals( owner.getBackground(), _outSideBackgroundColor) )
                    owner.setBackground(_outSideBackgroundColor);
                _outSideBackgroundColor = null;
            }
        }

        if ( !isStyled || !weNeedToInstallTheCustomUI ) {
            _dynamicLaF = _dynamicLaF._uninstallCustomLaF(owner);
            if ( owner instanceof AbstractButton && _initialContentAreaFilled != null ) {
                AbstractButton button = (AbstractButton) owner;
                if ( button.isContentAreaFilled() != _initialContentAreaFilled)
                    button.setContentAreaFilled(_initialContentAreaFilled);
                _initialContentAreaFilled = null;
            }
            if ( _initialIsOpaque != null ) {
                if ( owner.isOpaque() != _initialIsOpaque )
                    owner.setOpaque(_initialIsOpaque);
            }
            if ( !isStyled ) {
                backgroundSetter.run();
                if ( _initialFont != null && !Objects.equals(_initialFont, owner.getFont()) ) {
                    owner.setFont(_initialFont);
                    _initialFont = null;
                }
                _restoreForeground(owner);
                _restoreStyleOwnedSizesOf(owner);
                _restoreViewportBackgroundOf(owner);
                _updateViewportOpaquenessOf(owner, newStyle);

                return _updateEngine(owner, engine, newStyle);
            }
        }

        if ( _initialIsOpaque == null )
            _initialIsOpaque = initialIsOpaqueFlagState; // Important: Use the state of the flag before SwingTree did anything!

        if ( owner instanceof AbstractButton && _initialContentAreaFilled == null )
            _initialContentAreaFilled = initialIsContentAreaFilled;

        final Predicate<UI.ComponentArea> hasGradOrNoise = newStyle::hasOpaqueGradientsOrNoisesOn;
        final boolean hasBackgroundGradients             = newStyle.hasVisibleGradientsOnLayer(UI.Layer.BACKGROUND);
        final boolean hasBackgroundNoise                 = newStyle.hasVisibleNoisesOnLayer(UI.Layer.BACKGROUND);
        final boolean hasBackgroundPainters              = newStyle.hasPaintersOnLayer(UI.Layer.BACKGROUND);
        final boolean hasBackgroundImages                = newStyle.hasImagesOnLayer(UI.Layer.BACKGROUND);
        final boolean hasBackgroundShadows               = newStyle.hasVisibleShadows(UI.Layer.BACKGROUND);
        final boolean hasBorderRadius                    = newStyle.border().hasAnyNonZeroArcs();
        final boolean hasBackground                      = newStyle.base().backgroundColor().isPresent();
        final boolean hasMargin                          = newStyle.margin().isPositive();
        final boolean hasOpaqueBorder                    = newStyle.border().colors().isFullyOpaque();
        final boolean isNaturallyTransparent             = !_initialIsOpaque; // We categorize based on the initial state of the flag.
        final boolean backgroundIsActuallyBackground =
                                    !( owner instanceof JTabbedPane  ) && // The LaFs interpret the tab buttons as background
                                    !( owner instanceof JSlider      ) && // The track color is usually considered the background
                                    !( owner instanceof JProgressBar );   // also the progress track color is usually considered the background
                                    // TODO: Find and add more cases!

        if ( !hasBackground && _initialIsOpaque ) {
            // If the style has a border radius set we need to make sure that we have a background color:
            if ( hasBorderRadius || newStyle.border().margin().isPositive() ) {
                _outSideBackgroundColor = _outSideBackgroundColor != null ? _outSideBackgroundColor : owner.getBackground();
                newStyle = newStyle.backgroundColor(_outSideBackgroundColor);
            }
        }

        boolean hasUndefinedNullBackground = false;
        if ( hasBackground ) {
            boolean backgroundIsAlreadySet = Objects.equals( owner.getBackground(), newStyle.base().backgroundColor().get() );
            if ( !backgroundIsAlreadySet || StyleUtil.isUndefinedColor(newStyle.base().backgroundColor().get()) )
            {
                _outSideBackgroundColor = _outSideBackgroundColor != null ? _outSideBackgroundColor :  owner.getBackground();
                Color newColor = newStyle.base().backgroundColor()
                                                .filter( c -> !StyleUtil.isUndefinedColor(c) )
                                                .orElse(null);

                if ( newColor == null )
                    hasUndefinedNullBackground = true;

                backgroundSetter = () -> {
                    if ( newColor == null ) {
                        if ( owner.isBackgroundSet() )
                            owner.setBackground(null);
                    } else if ( !Objects.equals( owner.getBackground(), newColor ) )
                        owner.setBackground(newColor);
                };
                /*
                    This component is not a SwingTree component, which means that
                    the paint method is not overridden, and the style engine
                    cannot render the background of the component itself.
                    So we delegate this task to the look and feel.
                */
                if ( owner instanceof JScrollPane ) {
                    JScrollPane scrollPane = (JScrollPane) owner;
                    if ( scrollPane.getViewport() != null ) {
                        JViewport viewport = scrollPane.getViewport();
                        if ( !Objects.equals( viewport.getBackground(), newColor ) ) {
                            if ( _initialViewportBackground == null )
                                _initialViewportBackground = viewport.getBackground();
                            _handedDownViewportBackground = newColor;
                            viewport.setBackground( newColor );
                        }
                    }
                }
            }
        }

        boolean canBeOpaque = true;

        if ( _isTransparentConstant(owner.getBackground()) )
            canBeOpaque = false;

        if ( !hasGradOrNoise.test(UI.ComponentArea.ALL) ) {
            boolean hasOpaqueFoundation = 255 == newStyle.base().foundationColor().map(java.awt.Color::getAlpha).orElse(0);
            boolean hasOpaqueBackground = 255 == newStyle.base().backgroundColor().map( c -> !StyleUtil.isUndefinedColor(c) ? c : _outSideBackgroundColor).map(java.awt.Color::getAlpha).orElse(255);
            boolean hasBorder           = newStyle.border().widths().isPositive();

            if ( !hasOpaqueFoundation && !hasGradOrNoise.test(UI.ComponentArea.EXTERIOR) ) {
                if ( hasBorderRadius )
                    canBeOpaque = false;
                else if ( hasMargin )
                    canBeOpaque = false;
            }

            if ( hasBorder && (!hasOpaqueBorder && !hasGradOrNoise.test(UI.ComponentArea.BORDER)) )
                canBeOpaque = false;

            if (
                !hasOpaqueBackground &&
                !hasGradOrNoise.test(UI.ComponentArea.INTERIOR) &&
                !hasGradOrNoise.test(UI.ComponentArea.BODY)
            )
                canBeOpaque = false;
        }

        final Color   backgroundColor = owner.getBackground();
        final boolean backgroundIsFullyTransparent = backgroundColor == null || backgroundColor.getAlpha() == 0;
        final boolean customLookAndFeelInstalled = customLookAndFeelIsInstalled(owner);
        final boolean requiresBackgroundPainting =
                                             hasBackgroundGradients ||
                                             hasBackgroundNoise     ||
                                             hasBackgroundShadows   ||
                                             hasBackgroundPainters  ||
                                             hasBackgroundImages    ||
                                             hasBorderRadius        ||
                                             hasMargin;

        if ( _dynamicLaF.overrideWasNeeded() ) {
            if ( owner instanceof AbstractButton) {
                AbstractButton b = (AbstractButton) owner;

                boolean shouldButtonBeFilled =  !hasBackgroundImages &&
                        !hasBackgroundShadows &&
                        !hasBackground &&
                        !hasBackgroundGradients &&
                        !hasBackgroundNoise &&
                        !hasBackgroundPainters;

                if ( _initialContentAreaFilled != null && !_initialContentAreaFilled )
                    shouldButtonBeFilled = false;

                if ( shouldButtonBeFilled != b.isContentAreaFilled() )
                    b.setContentAreaFilled( shouldButtonBeFilled );
            }
        }

        if ( !canBeOpaque )
        {
            if ( owner.isOpaque() )
                owner.setOpaque(false);
        }
        else if ( !isSwingTreeComponent && !backgroundIsFullyTransparent && _initialIsOpaque != null )
        {
            if ( owner.isOpaque() != _initialIsOpaque )
                owner.setOpaque(_initialIsOpaque);
        }
        else if ( !isSwingTreeComponent && !backgroundWasSetSomewhereElse )
        {
            if ( owner.isOpaque() )
                owner.setOpaque(false);
        }
        else if (
            requiresBackgroundPainting &&
            ( !hasBackground || !customLookAndFeelInstalled ) &&
            ( backgroundWasSetSomewhereElse || !backgroundIsActuallyBackground )
        )
        {
            if ( owner.isOpaque() )
                owner.setOpaque(false);
        }
        else
        {
            boolean shouldBeOpaque = !_isTransparentConstant(owner.getBackground());
            boolean bypassLaFBackgroundPainting = requiresBackgroundPainting || (hasBackground && isSwingTreeComponent);

            if ( bypassLaFBackgroundPainting && backgroundIsActuallyBackground && !hasUndefinedNullBackground ) {
                backgroundSetter = () -> {
                    if ( !Objects.equals( owner.getBackground(), UI.Color.UNDEFINED ) )
                        owner.setBackground(UI.Color.UNDEFINED);
                };
                Color currentBackground = owner.getBackground();
                if ( !StyleUtil.isUndefinedColor(currentBackground) )
                    _overriddenBackgroundColor = currentBackground;
            }
            if ( !hasBackground && isNaturallyTransparent )
                shouldBeOpaque = false;
            if ( owner.isOpaque() != shouldBeOpaque )
                owner.setOpaque(shouldBeOpaque);
            /*
                The above line 'owner.setBackground(UI.Color.UNDEFINED);'
                may look very strange to you, but it is very important!

                To understand what is going on here, you have to know that when a component is
                flagged as opaque, then every Swing look and feel will, before painting
                anything else, first fill out the entire background of the component with
                the background color of the component.
                It does this to ensure that rendering artifacts from the parent
                are overridden.

                Now this is a problem when you have the background layer of your SwingTree component
                styled using various things like gradients, shadows, images, etc.
                Because SwingTree, unfortunately, cannot hijack the internals of the ComponentUI,
                it can however do some painting before the ComponentUI
                through an overridden `paint(Graphics2D)` method!

                Now, we could simply set the opaque flag to false in order to prevent the ComponentUI
                from filling the component bounds, but then we would lose the
                performance benefits of having the opaque flag set to true (avoiding the
                traversal repaint of parent components, and their parent components, etc).

                In this branch we have already determined that the style configuration
                leads to an opaque component, and we also have the ability to render
                the background of the component ourselves due to the
                component being a SwingTree component (it has the paint method overridden).

                So what we do here is we set the background color of the component to
                UI.Color.UNDEFINED, which is a special color that is actually fully transparent.

                This way, when the Swing look and feel tries to paint the background of the
                component, it will actually paint nothing, and we can do the background
                painting ourselves in the paint method of the component.
            */
        }

        _applyGenericBaseStyleTo(owner, newStyle);
        _applyForegroundStyleTo(owner, newStyle);
        _applyIconStyleTo(owner, newStyle);
        _applyLayoutStyleTo(owner, newStyle);
        _applyDimensionalityStyleTo(owner, newStyle);
        _applyPropertiesTo(owner, newStyle);
        _doComboBoxMarginAdjustment(owner, newStyle);

        if ( newStyle.hasPaintersOnLayer(UI.Layer.FOREGROUND) )
            _makeAllChildrenTransparent(owner);

        backgroundSetter.run();

        _updateViewportOpaquenessOf(owner, newStyle);
        /*
            Note that the above call has to happen after every code path which
            may still change the opaqueness or background color of the owner,
            because the viewport has to mirror the final state of the scroll pane.
        */

        StyleEngine newEngine = _updateEngine(owner, engine, newStyle);

        _applyFontStyleTo(owner, newStyle);

        return newEngine;
    }

    /**
     *  The viewport of a scroll pane is opaque by default, which is a promise to Swing
     *  that it will fill every single pixel of its bounds. A scroll pane, on the other hand,
     *  may very well be styled to be (partially) transparent, in which case the style engine
     *  hands the styled background color over to the viewport (see the {@code JScrollPane}
     *  case further above), because the viewport lies on top of the scroll pane background.
     *  <p>
     *  A viewport which fills its bounds with a transparent color paints little or nothing,
     *  and yet, as long as it is flagged as opaque, Swing will not repaint the ancestors
     *  behind it. Whatever was painted there before consequently survives every repaint,
     *  which makes the renderings of successive paints pile up into ever growing artifacts.
     *  This is especially noticeable when there are animations inside the scroll pane.
     *  <p>
     *  The reverse is just as important though: as soon as nothing needs to show through
     *  the viewport anymore, it has to reclaim its opaqueness, or else Swing's
     *  repaint manager keeps climbing past it to find an opaque ancestor, and every
     *  little repaint inside the scroll pane costs more than it should, forever.
     *  <p>
     *  Note that the opaqueness of the scroll pane itself answers a different question
     *  than the one asked here, so it must not be used as the signal:
     *  a scroll pane reports whether it fills every pixel of its own bounds, which a
     *  margin or a translucent border is enough to deny, even though both of them lie
     *  outside the viewport and leave the interior a single flat color.
     *  A look and feel may also hand out non-opaque scroll panes of its own accord
     *  (Nimbus does), and the style engine must not read that as a reason to take
     *  a flag it was never given.
     *
     * @param owner The component whose viewport should be synchronized, only scroll panes have one.
     * @param style The style of the owner, which tells us what an opaque viewport would break.
     */
    private void _updateViewportOpaquenessOf( final C owner, final StyleConf style ) {
        if ( !(owner instanceof JScrollPane) )
            return;

        JViewport viewport = ((JScrollPane) owner).getViewport();
        if ( viewport == null )
            return;

        Color   viewportBackground   = viewport.getBackground();
        boolean viewportFillsItsArea = viewportBackground != null && viewportBackground.getAlpha() == 255;
        boolean mayBeOpaque          = viewportFillsItsArea && !_anOpaqueViewportWouldBreak(style);
        /*
            Note that a viewport without its own background color inherits the one of
            the scroll pane, which the style engine may well have set to the fully
            transparent 'UI.Color.UNDEFINED' so that it can paint the background itself.
            The viewport cannot do that painting, so it also cannot claim to be opaque.
        */
        if ( !mayBeOpaque ) {
            if ( _initialViewportOpaque == null )
                _initialViewportOpaque = viewport.isOpaque();
            if ( viewport.isOpaque() )
                viewport.setOpaque(false);
        }
        else if ( _initialViewportOpaque != null ) {
            if ( viewport.isOpaque() != _initialViewportOpaque )
                viewport.setOpaque(_initialViewportOpaque);
            _initialViewportOpaque = null;
        }
        /*
            The style engine only gives back what it took: a viewport whose opaqueness
            was never taken away is left alone entirely, so that a look and feel, or the
            user, stays in charge of a scroll pane which has nothing to show through it.
        */
    }

    /**
     *  Determines if an opaque viewport would keep the supplied style from reaching the screen.
     *  <p>
     *  A viewport can only ever fill its bounds with a single flat color, and it is painted
     *  after the background, content and border layers of its scroll pane, so anything those
     *  layers draw would be covered. A border radius counts as well, because its rounded
     *  corners reach into the rectangular bounds of the viewport, and so does a filter on the
     *  parent, whose result is rendered into the background of the scroll pane.
     *  <p>
     *  The foreground layer is painted on top of the viewport and can therefore not be
     *  covered by it. It is still listed below, because an opaque viewport ends Swing's
     *  search for an ancestor to repaint, which would leave the foreground painting
     *  of the scroll pane stale as soon as a component inside the viewport repaints itself.
     *
     * @param style The style of a scroll pane.
     * @return {@code true} if an opaque viewport would hide the style or stop it from being refreshed.
     */
    private static boolean _anOpaqueViewportWouldBreak( final StyleConf style ) {
        return style.border().hasAnyNonZeroArcs()
            || !style.layers().filter().equals(FilterConf.none())
            || !style.layer(UI.Layer.BACKGROUND).isNone()
            || !style.layer(UI.Layer.CONTENT).isNone()
            || !style.layer(UI.Layer.BORDER).isNone()
            || !style.layer(UI.Layer.FOREGROUND).isNone();
    }

    /**
     *  Gives the viewport of a scroll pane its original background color back,
     *  the one the style engine took away from it when it handed the styled
     *  background color down to the viewport.
     *  Without this, a scroll pane whose styling was removed again would keep
     *  a translucent viewport, and with it the loss of opaqueness
     *  which {@link #_updateViewportOpaquenessOf(JComponent, StyleConf)} derives from it.
     *  The original color only comes back while the viewport still has the color the style
     *  engine handed down; any other color was set by something else in the meantime,
     *  like the application or a look and feel installed while the style was active, and stays.
     *
     * @param owner The component whose viewport should be restored, only scroll panes have one.
     */
    private void _restoreViewportBackgroundOf( final C owner ) {
        if ( !(owner instanceof JScrollPane) || _initialViewportBackground == null )
            return;

        JViewport viewport = ((JScrollPane) owner).getViewport();
        if (
            viewport != null &&
            Objects.equals( viewport.getBackground(), _handedDownViewportBackground ) &&
            !Objects.equals( viewport.getBackground(), _initialViewportBackground )
        ) {
            viewport.setBackground(_initialViewportBackground);
        }

        _initialViewportBackground = null;
        _handedDownViewportBackground = null;
    }

    @SuppressWarnings("ReferenceEquality")
    private final boolean _isTransparentConstant( final Color color ) {
        return color == UI.Color.TRANSPARENT;
    }

    @SuppressWarnings("ReferenceEquality")
    boolean backgroundWasChangedSomewhereElse( C owner ) {
        if ( _lastInsideBackgroundColor != null ) {
            if ( _lastInsideBackgroundColor != owner.getBackground() ) {
                return true;
            }
        }
        return false;
    }

    private void _applyGenericBaseStyleTo( final C owner, final StyleConf styleConf )
    {
        final BaseConf base = styleConf.base();

        base.cursor().ifPresent( cursor -> {
            if ( !Objects.equals( owner.getCursor(), cursor ) )
                owner.setCursor( cursor );
        });

        if ( base.orientation() != UI.ComponentOrientation.UNKNOWN ) {
            ComponentOrientation currentOrientation = owner.getComponentOrientation();
            UI.ComponentOrientation newOrientation = base.orientation();
            switch ( newOrientation ) {
                case LEFT_TO_RIGHT:
                    if ( !Objects.equals( currentOrientation, ComponentOrientation.LEFT_TO_RIGHT ) )
                        owner.applyComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
                    break;
                case RIGHT_TO_LEFT:
                    if ( !Objects.equals( currentOrientation, ComponentOrientation.RIGHT_TO_LEFT ) )
                        owner.applyComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
                    break;
                default:
                    if ( !Objects.equals( currentOrientation, ComponentOrientation.UNKNOWN ) )
                        owner.applyComponentOrientation(ComponentOrientation.UNKNOWN);
                    break;
            }
        }
    }

    private void _applyIconStyleTo( final C owner, StyleConf styleConf )
    {
        final BaseConf base = styleConf.base();

        UI.FitComponent fit = base.fit();
        base.icon().ifPresent( icon -> {
            if ( icon instanceof SvgIcon) {
                SvgIcon svgIcon = (SvgIcon) icon;
                icon = svgIcon.withFitComponent(fit);
            }
            if ( owner instanceof AbstractButton ) {
                AbstractButton button = (AbstractButton) owner;
                if ( !Objects.equals( button.getIcon(), icon ) )
                    button.setIcon( icon );
            }
            if ( owner instanceof JLabel ) {
                JLabel label = (JLabel) owner;
                if ( !Objects.equals( label.getIcon(), icon ) )
                    label.setIcon( icon );
            }
            if ( owner instanceof JIcon ) {
                JIcon jIcon = (JIcon) owner;
                if ( !Objects.equals( jIcon.getIcon(), icon ) )
                    jIcon.setIcon( icon );
            }
        });
    }

    private void _applyLayoutStyleTo( final C owner, final StyleConf style )
    {
        final LayoutConf layoutConf = style.layout();
        // Generic Layout stuff:

        layoutConf.alignmentX().ifPresent( alignmentX -> {
            if ( !Objects.equals( owner.getAlignmentX(), alignmentX ) )
                owner.setAlignmentX( alignmentX );
        });

        layoutConf.alignmentY().ifPresent( alignmentY -> {
            if ( !Objects.equals( owner.getAlignmentY(), alignmentY ) )
                owner.setAlignmentY( alignmentY );
        });

        // Install Generic Layout:
        layoutConf.layout().installFor(owner);

        // Now on to MigLayout installation details:

        Optional<Float> alignmentX = layoutConf.alignmentX();
        Optional<Float> alignmentY = layoutConf.alignmentY();

        if ( !alignmentX.isPresent() && !alignmentY.isPresent() )
            return;

        LayoutManager layoutManager = ( owner.getParent() == null ? null : owner.getParent().getLayout() );
        if ( layoutManager instanceof MigLayout ) {
            MigLayout migLayout = (MigLayout) layoutManager;
            Object rawComponentConstraints = migLayout.getComponentConstraints(owner);
            if ( rawComponentConstraints instanceof String )
                rawComponentConstraints = ConstraintParser.parseComponentConstraint(rawComponentConstraints.toString());

            CC componentConstraints = (rawComponentConstraints instanceof CC ? (CC) rawComponentConstraints : null);

            final CC finalComponentConstraints = ( componentConstraints == null ? new CC() : componentConstraints );

            String x = alignmentX.map( a -> (int) ( a * 100f ) )
                                  .map( a -> a + "%" )
                                  .orElse("");

            String y = alignmentY.map( a -> (int) ( a * 100f ) )
                                  .map( a -> a + "%" )
                                  .orElse("");

            DimConstraint horizontalDimConstraint = finalComponentConstraints.getHorizontal();
            DimConstraint verticalDimConstraint   = finalComponentConstraints.getVertical();

            UnitValue xAlign = horizontalDimConstraint.getAlign();
            UnitValue yAlign = verticalDimConstraint.getAlign();

            boolean xChange = !x.equals( xAlign == null ? "" : xAlign.getConstraintString() );
            boolean yChange = !y.equals( yAlign == null ? "" : yAlign.getConstraintString() );

            if ( !x.isEmpty() && xChange )
                finalComponentConstraints.alignX(x);

            if ( !y.isEmpty() && yChange )
                finalComponentConstraints.alignY(y);

            if ( xChange || yChange ) {
                migLayout.setComponentConstraints(owner, finalComponentConstraints);
                owner.getParent().revalidate();
            }
        }
    }

    /**
     *  Restores the component's minimum/maximum sizes to what they were before the style engine first
     *  overrode them. This is needed because the style engine sets these sizes additively; without an
     *  explicit restore, a stale override (e.g. one left behind by a transitional/animated style such
     *  as a fold animation) would keep the component from shrinking to fit its content.
     */
    private void _restoreStyleOwnedSizesOf( final C owner ) {
        boolean changed = false;
        if ( _styleOwnsMinSize ) {
            owner.setMinimumSize(_initialMinSize);
            _styleOwnsMinSize = false;
            _initialMinSize = null;
            changed = true;
        }
        if ( _styleOwnsMaxSize ) {
            owner.setMaximumSize(_initialMaxSize);
            _styleOwnsMaxSize = false;
            _initialMaxSize = null;
            changed = true;
        }
        if ( changed && owner.getParent() != null )
            owner.getParent().revalidate();
    }

    private void _applyDimensionalityStyleTo( final C owner, final StyleConf styleConf )
    {
        final DimensionalityConf dimensionalityConf = styleConf.dimensionality();

        if ( dimensionalityConf.minWidth().isPresent() || dimensionalityConf.minHeight().isPresent() ) {
            if ( !_styleOwnsMinSize ) {
                _initialMinSize = owner.isMinimumSizeSet() ? owner.getMinimumSize() : null;
                _styleOwnsMinSize = true;
            }
            Dimension minSize = owner.getMinimumSize();

            int minWidth  = dimensionalityConf.minWidth().orElse(minSize == null ? 0 : minSize.width);
            int minHeight = dimensionalityConf.minHeight().orElse(minSize == null ? 0 : minSize.height);

            Dimension newMinSize = new Dimension(minWidth, minHeight);

            if ( ! newMinSize.equals(minSize) )
                owner.setMinimumSize(newMinSize);
        }
        else if ( _styleOwnsMinSize ) {
            // The style no longer specifies a minimum size, so we restore the original one.
            // Without this, a stale override (e.g. left by a transitional/animated style)
            // would keep the component from shrinking to fit its (possibly reduced) content.
            owner.setMinimumSize(_initialMinSize);
            _styleOwnsMinSize = false;
            _initialMinSize = null;
            if ( owner.getParent() != null )
                owner.getParent().revalidate();
        }

        if ( dimensionalityConf.maxWidth().isPresent() || dimensionalityConf.maxHeight().isPresent() ) {
            if ( !_styleOwnsMaxSize ) {
                _initialMaxSize = owner.isMaximumSizeSet() ? owner.getMaximumSize() : null;
                _styleOwnsMaxSize = true;
            }
            Dimension maxSize = owner.getMaximumSize();

            int maxWidth  = dimensionalityConf.maxWidth().orElse(maxSize == null  ? Integer.MAX_VALUE : maxSize.width);
            int maxHeight = dimensionalityConf.maxHeight().orElse(maxSize == null ? Integer.MAX_VALUE : maxSize.height);

            Dimension newMaxSize = new Dimension(maxWidth, maxHeight);

            if ( !newMaxSize.equals(maxSize) )
                owner.setMaximumSize(newMaxSize);
        }
        else if ( _styleOwnsMaxSize ) {
            owner.setMaximumSize(_initialMaxSize);
            _styleOwnsMaxSize = false;
            _initialMaxSize = null;
            if ( owner.getParent() != null )
                owner.getParent().revalidate();
        }

        if ( dimensionalityConf.preferredWidth().isPresent() || dimensionalityConf.preferredHeight().isPresent() ) {
            Dimension prefSize = owner.getPreferredSize();

            int prefWidth  = dimensionalityConf.preferredWidth().orElse(prefSize == null ? 0 : prefSize.width);
            int prefHeight = dimensionalityConf.preferredHeight().orElse(prefSize == null ? 0 : prefSize.height);

            Dimension newPrefSize = new Dimension(prefWidth, prefHeight);

            if ( !newPrefSize.equals(prefSize) ) {
                owner.setPreferredSize(newPrefSize);
                // Trigger a re-layout of the parent container, because preferred size changes can affect the layout:
                if ( owner.getParent() != null )
                    owner.getParent().revalidate();
            }
            // Note: unlike the minimum/maximum size, we intentionally do NOT remember and restore the
            // preferred size when the style stops specifying it. The preferred size is only a hint and
            // is also driven by the auto preferred height feature (TextConf#autoPreferredHeight), which
            // expects the last computed value to remain in place when it is switched off.
        }

        if ( dimensionalityConf.width().isPresent() || dimensionalityConf.height().isPresent() ) {
            Dimension size = owner.getSize();

            int width  = dimensionalityConf.width().orElse(size == null ? 0 : size.width);
            int height = dimensionalityConf.height().orElse(size == null ? 0 : size.height);

            Dimension newSize = new Dimension(width, height);

            if ( !newSize.equals(size) )
                owner.setSize(newSize);
        }
    }

    private void _applyFontStyleTo( final C owner, final StyleConf styleConf )
    {
        final FontConf fontConf = styleConf.font();
        if ( FontConf.none().equals(fontConf) ) {
            if ( _initialFont != null && !Objects.equals(_initialFont, owner.getFont()) ) {
                owner.setFont(_initialFont);
                _initialFont = null;
            }
            return;
        } else if ( _initialFont == null ) {
            _initialFont = owner.getFont();
        }

        if ( owner instanceof JTextComponent ) {
            JTextComponent tc = (JTextComponent) owner;
            if ( fontConf.selectionColor().isPresent() && ! Objects.equals( tc.getSelectionColor(), fontConf.selectionColor().get() ) )
                tc.setSelectionColor(fontConf.selectionColor().get());
        }

        fontConf
             .createDerivedWithoutSolidColorFrom(owner.getFont(), owner)
             .ifPresent( newFont -> {
                    if ( !newFont.equals(owner.getFont()) )
                        owner.setFont( newFont );
                });

        _installLayoutInfoFromFontConf(fontConf, owner);
    }

    /**
     *  A style sets the foreground of a component in two ways: with the base 'foregroundColor',
     *  and with a solid font color, which is the more specific of the two and wins.
     *  A solid font color deliberately does not travel inside the font: it would flip
     *  'Font.hasLayoutAttributes()' and put every measure/draw of this component on the
     *  expensive TextLayout path, and it would override the LaF's state colors.
     *  When the style sets neither, the foreground from before the style comes back.
     */
    private void _applyForegroundStyleTo( final C owner, final StyleConf styleConf ) {
        final Color fontColor = styleConf.font().solidColor();
        if ( fontColor != null )
            _installForeground(owner, fontColor);
        else if ( styleConf.base().foregroundColor().isPresent() )
            _installForeground(owner, styleConf.base().foregroundColor().get());
        else
            _restoreForeground(owner);
    }

    private void _installForeground( final C owner, final Color color ) {
        if ( _installedForeground == null || !_hasOwnForeground(owner, _installedForeground) )
            _initialForeground = owner.isForegroundSet() ? owner.getForeground() : UI.Color.UNDEFINED;
        if ( !_hasOwnForeground(owner, color) )
            owner.setForeground( StyleUtil.isUndefinedColor(color) ? null : color );
        _installedForeground = color;
    }

    private void _restoreForeground( final C owner ) {
        if ( _initialForeground == null || _installedForeground == null )
            return;
        if ( _hasOwnForeground(owner, _installedForeground) && !_hasOwnForeground(owner, _initialForeground) )
            owner.setForeground( StyleUtil.isUndefinedColor(_initialForeground) ? null : _initialForeground );
        _initialForeground   = null;
        _installedForeground = null;
    }

    private static boolean _hasOwnForeground( final JComponent owner, final Color color ) {
        if ( StyleUtil.isUndefinedColor(color) )
            return !owner.isForegroundSet();
        return owner.isForegroundSet() && color.equals(owner.getForeground());
    }

    @SuppressWarnings("DoNotCall")
    private static void _installLayoutInfoFromFontConf(FontConf fontConf, JComponent owner) {
        LibraryInternalCrossPackageStyleUtil.applyFontConfAlignmentsToComponent(fontConf, owner);
    }

    private void _applyPropertiesTo( final C owner, final StyleConf styleConf ) {
        styleConf.properties().forEach( property -> {
            Object oldValue = owner.getClientProperty(property.name());
            if ( property.style().equals(oldValue) )
                return;

            if ( property.style().isEmpty() )
                owner.putClientProperty(property.name(), null); // remove property
            else
                owner.putClientProperty(property.name(), property.style());
        });
    }

    private void _doComboBoxMarginAdjustment( final C owner, final StyleConf styleConf ) {
        if ( owner instanceof JComboBox ) {
            int bottom = styleConf.margin().bottom().map(Number::intValue).orElse(0);
            // We adjust the position of the popup menu:
            try {
                Point location = owner.getLocationOnScreen();
                int x = location.x;
                int y = location.y + owner.getHeight() - bottom;
                JComboBox<?> comboBox = (JComboBox<?>) owner;
                JPopupMenu popup = (JPopupMenu) comboBox.getAccessibleContext().getAccessibleChild(0);
                Point oldLocation = popup.getLocation();
                if ( popup.isShowing() && (oldLocation.x != x || oldLocation.y != y) )
                    popup.setLocation(x, y);
            } catch ( Exception e ) {
                // ignore
            }
        }
    }

    private void _uninstallCustomBorderBasedStyleAndAnimationRenderer( C owner ) {
        Border currentBorder = owner.getBorder();
        if ( currentBorder == null )
            return;

        if ( currentBorder instanceof StyleAndAnimationBorder) {
            StyleAndAnimationBorder<?> border = (StyleAndAnimationBorder<?>) currentBorder;
            owner.setBorder(border.getFormerBorder());
            border.restoreBorderPaintedFlagOfOwner();
        }
    }

    /**
     *  Note that the foreground painter is intended to paint over all children of the component, <br>
     *  which is why it will be called at the end of {@code JComponent::paintChildren(Graphics)}.
     *  <br>
     *  However, there is a problem with this approach! <br>
     *  If not all children are transparent, the result of the foreground painter can be overwritten
     *  by {@link JComponent#paintImmediately(int, int, int, int)} when certain events occur
     *  (like a child component is a text field with a blinking cursor, or a button with hover effect).
     *  This type of repaint does unfortunately not call {@code JComponent::paintChildren(Graphics)},
     *  in fact it completely bypasses the rendering of this current component!
     *  In order to ensure that the stuff painted by the foreground painter is not overwritten
     *  in these types of cases,
     *  we make all children transparent (non-opaque) so that the foreground painter is always visible.
     *
     * @param c The component to make all children transparent.
     */
    private void _makeAllChildrenTransparent( JComponent c ) {
        if ( !c.isVisible() )
            return;

        if ( c.isOpaque() )
            c.setOpaque(false);

        for ( Component child : c.getComponents() ) {
            if ( child instanceof JComponent ) {
                JComponent jChild = (JComponent) child;
                _makeAllChildrenTransparent(jChild);
            }
        }
    }

    /**
     *  The background a component type has before anything styles it is whatever the look and
     *  feel installs on a fresh instance of that type, so a fresh instance is what this asks.
     *  <p>
     *  It asks the nearest JDK or SwingTree superclass rather than the component's own class,
     *  because the component's own class may be the application's, and constructing an
     *  application's view runs that view's constructor with every side effect it has. One such
     *  constructor installs a look and feel, so building a window that contained it replaced
     *  the look and feel the application had just chosen, and the whole window was then drawn
     *  by the wrong delegates. Styling reads state; it must not create application objects to
     *  do it.
     *  <p>
     *  The answer for an application subclass is therefore the one its nearest library
     *  ancestor gets - which is what the look and feel would have installed on it anyway,
     *  since a look and feel keys its defaults off the component type it knows about.
     *
     * @param type the class of the component whose default background is wanted
     * @return the nearest class that may be constructed to answer the question, or null if
     *         there is none - every candidate up to {@link JComponent} being abstract
     */
    private static @Nullable Class<?> _nearestLibraryAncestorOf( Class<?> type ) {
        for ( Class<?> candidate = type; candidate != null; candidate = candidate.getSuperclass() ) {
            if ( Modifier.isAbstract(candidate.getModifiers()) )
                continue;
            String name = candidate.getName();
            if ( name.startsWith("java.") || name.startsWith("javax.") || name.startsWith("swingtree.") )
                return candidate;
        }
        return null;
    }

    private void _establishDefaultBackgroundColorFor(JComponent owner) {
        Class<?> type = _nearestLibraryAncestorOf(owner.getClass());
        JComponent other = null;
        try {
            if ( type != null )
                other = (JComponent) type.getDeclaredConstructor().newInstance();
        } catch (Exception e) {
            log.debug(SwingTree.get().logMarker(),
                    "Failed to instantiate component '{}' as part of an " +
                    "attempt to get the default color of said type!",
                    type == null ? owner.getClass().getName() : type.getName(), e
                );
        }
        Color defaultBackgroundColor = null;
        if ( other != null ) {
            defaultBackgroundColor = other.getBackground();
        }
        if ( defaultBackgroundColor == null ) {
            if ( owner.isBackgroundSet() ) // is this is false then the component already has it set to null!
                owner.setBackground(defaultBackgroundColor);
        } else {
            if ( !Objects.equals(owner.getBackground(), defaultBackgroundColor) )
                owner.setBackground(defaultBackgroundColor);
        }
    }

}