globaltcad / swing-tree

A small DSL library for building Swing UIs
MIT License
6 stars 0 forks source link

TransitionalStyle not applied correctly #202

Closed Mazi-S closed 2 weeks ago

Mazi-S commented 1 month ago

If a component has a TransitionalStyle and is not displayed when the style changes, the style is incorrect when the component is displayed again.

It seems that when the component is displayed, the lambda is executed correctly once, but the style is not applied. My wild guess is that a revalidate() is missing in swingtree.style.StyleInstaller#_applyDimensionalityStyleTo, but I'm not familiar with the code.

It is a bit hard to explain, but I will push example that makes it clear.

Gleethos commented 1 month ago

If a component has a TransitionalStyle and is not displayed

Ok, so let me give you some overarching background information to give you a picture:

So I have the suspicion that this is probably based on the fundamental problem that Swing does not really give SwinTree a clean place and time where to hook into and update itself irrespective of the component state.

There are 4 places where we currently hook into the update cycle of a component to trigger all of the Styler lambda stuff and ultimately the StyleInstaller.

The first one is the StyleAndAnimationBorder, it renders the border layer and also delegates to any former border of the component. For some components SwingTree has custom ComponentUI implementations (see DynamicLaF in the StyleInstaller).

And the last two places are found in implementations of the StylableComponent interface usually the ones declared in the UI class. So for example:

    /** {inheritDoc} */
    public static class Slider extends JSlider implements StylableComponent {
        @Override public void paintComponent(Graphics g){ paintBackground(g, super::paintComponent); }
        @Override public void paintChildren(Graphics g) { paintForeground(g, super::paintChildren); }
        @Override public void setUISilently( ComponentUI ui ) { this.ui = ui; }
    }

SwingTree works well with any of these hooks. A single one is enough to trigger an update for the whole styling stuff to take affect...

The problem with all of these hooks however is that they are at the mercy of the public void paint(Graphics g) method of the JComponent. This is what orchestrates calls to the formerly mentioned places...

But this method may not decide to continue its execution in all cases

    public void paint(Graphics g) {
        boolean shouldClearPaintFlags = false;

        if ((getWidth() <= 0) || (getHeight() <= 0)) {
            return;
        }
        [...]
        [...]
    }

This is why you will encounter the following condition in the AnimationRunner:

if ( component.getParent() == null || (component.getWidth() <= 0) || (component.getHeight() <= 0) ) {
    ComponentExtension.from((JComponent) component).gatherApplyAndInstallStyle(false);
    component.revalidate();
    component.repaint();
}

So the first problem you outline might be because somewhere in the Swing code it checks if JComponent::isVisible is false and then just returns without ever triggering a paint cycle (which makes total sense! But it is unfortunate for the SwingTree logic).

So from having done debugging before, my assumption is that maybe the above condition should also check for the visibility of the component. But this is just a guess.


It seems that when the component is displayed, the lambda is executed correctly once, but the style is not applied. My wild guess is that a revalidate() is missing in swingtree.style.StyleInstaller#_applyDimensionalityStyleTo, but I'm not familiar with the code.

No this is unexpected and I would assume the layout manager to react to the size changes automatically. At least, that is what the assumption is in the StyleInstaller code. So you might be right that there is indeed a revalidate() missing there.

But we should be careful with these statements in there, because there is a danger of triggering repeated repaint/revalidate cycles...

p10n commented 1 month ago

Maybe invalidate() is enough?

Gleethos commented 2 weeks ago

What exactly is the issue you are facing here in terms of a scenario. How can I reproduce this?

Mazi-S commented 2 weeks ago

If you have two panels with a transitional style based on a the same property (a foldable panel) placed in different tabs.

.withTransitionalStyle(isOpen, LifeTime.of(0.5, TimeUnit.SECONDS), (status, style) -> {
    double h = style.component().getPreferredSize().height * status.fadeIn();
    style.component().setBackground( h == 0 ? Color.orange : Color.lightGray);
    System.out.printf("TransitionalStyle[%s]: h=%f\n", content, h);
    return style.minHeight(h).maxHeight(h);
})

The invisible foldable panel one is not displayed correctly after the property changes.

Initial state. Both expanded. Screenshot from 2024-11-12 09-42-41 Screenshot from 2024-11-12 09-42-48

After clicking the Show button on the first tab. Screenshot from 2024-11-12 09-43-41image

Another invalid state. The BG color seems to be displayed correctly. Only the size seems to be affected. image

When both are displayed, everything works perfectly:

Screenshot from 2024-11-12 10-00-14 Screenshot from 2024-11-12 10-00-29

Framework: We want to synchronize the run panels between tabs.