mgarin / weblaf

WebLaF is a fully open-source Look & Feel and component library written in pure Java for cross-platform desktop Swing applications.
http://weblookandfeel.com
GNU General Public License v3.0
1.13k stars 234 forks source link

Ability to provide StyleId for each particular tab in JTabbedPane #578

Closed mgarin closed 4 years ago

mgarin commented 4 years ago

Right now there is no easy way to restyle any particular tab, tab style can only be modified based on existing states (hover/selected/disabled).

A good way would be adding setStyleIdAt ( int, StyleId ) method in WebTabbedPane to allow customization of any particular tab to your liking with a custom style.

It is important to note that the custom style would need to provide all default decorations or extend existing default style, otherwise tab will have no extra decorations except for the provided ones. I'll add some examples here once this feature is implemented.

mgarin commented 4 years ago

I quickly checked if this can be implemented added it right away because I didn't require any extensive changes at all. You will now be able to provide StyleId for any particular Tab component that are used in WebLaF's JTabbedPane UI implementation.


To give a small example, here is a random tabbed pane:

public class TabStyleTest
{
    public static void main ( final String[] args )
    {
        SwingTest.run ( new Runnable ()
        {
            @Override
            public void run ()
            {
                final WebTabbedPane tabbedPane = new WebTabbedPane ();
                tabbedPane.addTab ( "Tab 1", WebLookAndFeel.getIcon ( 16 ), createContent () );
                tabbedPane.addTab ( "Tab 2", WebLookAndFeel.getIcon ( 16 ), createContent () );
                tabbedPane.addTab ( "Tab 3", WebLookAndFeel.getIcon ( 16 ), createContent () );
                tabbedPane.addTab ( "Tab 4", WebLookAndFeel.getIcon ( 16 ), createContent () );
                TestFrame.show ( tabbedPane );
            }

            private Component createContent ()
            {
                final JLabel label = new JLabel ( "Sample content", JLabel.CENTER );
                label.setPreferredSize ( new Dimension ( 400, 300 ) );
                return label;
            }
        } );
    }
}

image

There are two methods now available in WebTabbedPane to change Tab's style:

    /**
     * Changes {@link StyleId} for {@link Tab} component at the specified index.
     * Use this method to provide a standalone {@link StyleId} for any particular {@link Tab}.
     *
     * @param index   {@link Tab} index
     * @param styleId new {@link StyleId}
     */
    public void setStyleIdAt ( final int index, @NotNull final StyleId styleId )
    {
        firePropertyChange ( STYLE_ID_AT_PROPERTY, null, new Object[]{ index, styleId } );
    }

    /**
     * Changes {@link StyleId} for {@link Tab} component at the specified index.
     * Use this method to provide a {@link StyleId} with {@link TabContainer} as parent for any particular {@link Tab}.
     * Resulting {@link StyleId} will be retrieved through {@link ChildStyleId#at(JComponent)} with {@link TabContainer} provided.
     *
     * @param index   {@link Tab} index
     * @param styleId new {@link ChildStyleId}, {@link TabContainer} will become it's parent
     */
    public void setStyleIdAt ( final int index, @NotNull final ChildStyleId styleId )
    {
        firePropertyChange ( STYLE_ID_AT_PROPERTY, null, new Object[]{ index, styleId } );
    }

First, here is what will happen with a custom standalone style:

tabbedPane.setStyleIdAt ( 1, StyleId.styledlabelVerticalCCW );

Note: Since each Tab (component that is used for representing each tab in JTabbedPane) is basically a WebStyledLabel - I simply used an existing styledlabel style with text rotation.

Result:

image

It obviously lost all custom tab decorations because normal styledlabel styles do not support custom Tab states. This will most probably not suffice for any particular case, so there are two options:


  1. Make a standalone Tab style

First you will need a custom styledlabel style:

    <!-- Custom tab title -->
    <style type="styledlabel" id="mytab" padding="4">
        <painter>
            <decorations>
                <decoration>
                    <WebShape round="3" sides="1,1,0,1" />
                    <WebShadow type="outer" width="2" />
                    <LineBorder color="170,170,170" />
                    <GradientBackground>
                        <color>white</color>
                        <color>223,223,223</color>
                    </GradientBackground>
                    <LabelLayout>
                        <LabelIcon constraints="icon" />
                        <TabText constraints="text" color="60,60,60" />
                    </LabelLayout>
                </decoration>
                <decoration states="hover">
                    <ColorBackground color="210,210,210" />
                    <LabelLayout>
                        <TabText constraints="text" color="black" />
                    </LabelLayout>
                </decoration>
            </decorations>
        </painter>
    </style>

Next you provide it for the particular tab:

tabbedPane.setStyleIdAt ( 1, StyleId.of ( "mytab" ) );

This is an oversimplified example, but it works for our basic case:

image

Pros: No need to deal with complicated default Tab styling Cons: Might be a lot of work (or copy-paste) if you want to preserve most of default Tab styling


  1. Customize existing Tab style

First you will need to make a custom tabbedpane style with an extra tab style in it:

    <!-- Tabbed pane with extra custom tab styles -->
    <style type="tabbedpane" id="mytabbedpane">
        <style type="panel" id="tab-area">
            <style type="viewport" id="viewport">
                <style type="panel" id="container">

                    <!-- Bridge to the default tab style so we can extend it below -->
                    <style type="styledlabel" id="tab" />

                    <!-- Our custom extra tab title style -->
                    <style type="styledlabel" id="mytab" extends="tab">
                        <painter>
                            <decorations>
                                <decoration states="has-no-children,selected">
                                    <ColorBackground color="41,33,114" />
                                    <LabelLayout>
                                        <TabText color="white" />
                                    </LabelLayout>
                                </decoration>
                                <decoration states="has-no-children,selected,in-focused-parent">
                                    <LabelLayout>
                                        <TabText color="white" />
                                    </LabelLayout>
                                </decoration>
                            </decorations>
                        </painter>
                    </style>

                </style>
            </style>
        </style>
    </style>

And we will need to define both JTabbedPane and particular tab styles:

final WebTabbedPane tabbedPane = new WebTabbedPane ( StyleId.of ( "mytabbedpane" ) );

...

tabbedPane.setStyleIdAt ( 1, ChildStyleId.of ( "mytab" ) );

Result:

image image

This will probably work better for simple cases when tab style needs to be slightly customized and not fully replaced. It does look more complicated, but mostly due to a large styles structure that is used for all different JTabbedPane parts - that structure have to be preserved in the custom style so that we can keep the overall default look of JTabbedPane.

Pros: Default Tab styling is preserved unless explicitly modified Cons: More complicated to setup since custom StyleId also needs to be provided in JTabbedPane itself on top of custom StyleId for particular Tab


You can also use 1st approach and simply fully copy-paste the default tab style:

<!-- Tab title -->
<style type="styledlabel" id="tab" padding="6,10,6,8">
    <component>
        <horizontalAlignment>0</horizontalAlignment>
    </component>
    <painter>
        <decorations overwrite="true">
            <decoration>
                <BoundsShape />
                <ColorBackground color="237,237,237" />
                <Stripes orientation="horizontal" align="bottom" bounds="margin">
                    <Stripe>
                        <color>170,170,170</color>
                    </Stripe>
                </Stripes>
            </decoration>
            <decoration states="left">
                <Stripes orientation="vertical" align="right" bounds="margin" />
            </decoration>
            <decoration states="right">
                <Stripes orientation="vertical" align="left" bounds="margin" />
            </decoration>
            <decoration states="bottom">
                <Stripes align="top" />
            </decoration>
            <decoration states="enabled,hover">
                <ColorBackground color="218,218,218" />
            </decoration>
            <decoration states="selected">
                <ColorBackground color="white" />
                <Stripes>
                    <Stripe stroke="basic;5">
                        <color>156,167,184</color>
                    </Stripe>
                </Stripes>
            </decoration>
            <decoration states="enabled,selected,hover">
                <ColorBackground color="218,218,218" />
            </decoration>
            <decoration states="selected,in-focused-parent">
                <Stripes>
                    <Stripe stroke="basic;5">
                        <color>64,131,201</color>
                    </Stripe>
                </Stripes>
            </decoration>
            <decoration states="has-no-children">
                <LabelLayout>
                    <LabelIcon constraints="icon" />
                    <TabText constraints="text" color="60,60,60" />
                </LabelLayout>
            </decoration>
            <decoration states="has-no-children,hover">
                <LabelLayout>
                    <TabText color="black" />
                </LabelLayout>
            </decoration>
            <decoration states="has-no-children,selected">
                <LabelLayout>
                    <TabText color="black" />
                </LabelLayout>
            </decoration>
            <decoration states="has-no-children,selected,in-focused-parent">
                <LabelLayout>
                    <TabText color="0,50,160" />
                </LabelLayout>
            </decoration>
            <decoration states="has-no-children,disabled">
                <LabelLayout>
                    <TabText color="180,180,180" ignoreCustomColor="true" ignoreStyleColors="true" />
                </LabelLayout>
            </decoration>
        </decorations>
    </painter>
</style>

But I would not recommend that unless you are ready to copy-paste all future changes/adjustments in the default tab style to your custom tab style. It will become exponentially harder with each new skin you are going to support.

mgarin commented 4 years ago

There is a small catch with styling specific tabs though - unfortunately the menu available with tab scroll layout would still display the default styling for all tabs, including the one you customized.

I might look into possible options later on, but overall it would be hard to workaround that in current implementation without providing a separate custom style for the menu item. It might generally not be worth the hassle.