-
Notifications
You must be signed in to change notification settings - Fork 235
Styling introduction
Available since WebLaF v1.2.9 release, updated for WebLaF v1.2.12 release
Requires weblaf-ui module and Java 6 update 30 or any later to run
Starting with v1.2.9 update WebLaF provides proper styling support for each Swing and extended WebLaF component. New styling system allows you to style any component within your application using predefined skins and styles or custom ones which you can easily define on your own.
Styling system consists of four key elements:
- Skins
- Skin Extensions
- Styles
- Painters
To fully understand how this system works you need to understand each of these key parts, so first of all let's look at what each of them does and how they can be used.
Skins exist to group multiple component styles together and provide convenient mechanisms to access styles for the L&F. Each skin can contain some general information like identifier, class, supported systems, icon, title, description and author. Also each skin usually contains either some styles or includes for separate XML files that contain styles.
Each actual skin must implement Skin
interface, but there is ready-to-use XmlSkin
class available in the WebLaF ui
module which is a XML-based implementations for Skin
interface. We will stick with this implementation for the main part of introduction as it is the easiest way to start.
There is also AbstractSkin
class that has basic implementation for a few common methods, but you will still have to provide styles for each existing components if you are using it.
You will be able to configure skins trough the code in the future (#427), but for now you need to define them separately in a special XML format. Here is a small example taken from default WebLightSkin
of how skin currently look like in XML:
<skin xmlns="http://weblookandfeel.com/XmlSkin">
<!-- Skin settings -->
<id>weblaf.light.skin</id>
<class>com.alee.skin.light.WebLightSkin</class>
<supportedSystems>all</supportedSystems>
<!-- Skin information -->
<icon>icons/icon.png</icon>
<title>Light skin</title>
<description>Light WebLaF UI skin</description>
<author>Mikle Garin</author>
<!-- Icon sets -->
<iconSet>com.alee.iconset.LightIconSet</iconSet>
<!-- Styles placed in strict initialization order -->
<include>resources/canvas.xml</include>
<include>resources/image.xml</include>
<include>resources/label.xml</include>
...
</skin>
Skin settings block is necessary for the main skin file but can be skipped in the included files.
Skin information block is optional and simply contains some information about the skin and it's author.
Icon sets, includes and styles are all optional, but you have to define all required styles in each skin you create, whether it is in includes or directly in the main skin XML file. Default skins you can find in WebLaF use includes to structure styles better and avoid a single huge XML file that would be hard to edit.
I won't be explaining icon set includes here, but you can read more about them and IconManager
in general in this article.
While skins allow deep customization for all components - they are not always the most convenient and fastest thing to use, mostly because you have to define all required styles in each skin you create. There is of course an option to include existing WebLaF skin as a base, but it might not be the best solution either.
Skin extensions are similar to skins because they can have custom styles in them just like skins and they can extend styles from the skins they extend, but they never define any default styles and they cannot override any styles from the skin they extend. It might sound complicated, but it's actually pretty simple.
First of all, let's look at an extension example:
<skin xmlns="http://weblookandfeel.com/XmlSkinExtension">
<!-- Extension settings -->
<id>weblaf.demo.skin.extension</id>
<class>com.alee.demo.skin.DemoAdaptiveExtension</class>
<extends>weblaf.light.skin</extends>
<extends>weblaf.dark.skin</extends>
...
</skin>
This is a part of an actual extension used by WebLaF's DemoApplication
. Just like a skin - it has an id
and class
fields, it can also have information about it and author.
It can also have multiple <extends>...</extends>
definitions (with skin identifiers in them) which basically tell which skins this extension can apply to. And yes, each extension can be applied to multiple skins at once - that can save a lot of time if your custom style doesn't need to define any skin-specific view settings - you can simply make it cross-skin by adding it into such extension. That is basically what DemoApplication
does - it has three extensions, one adaptive that works for two default skins and one custom for each of the skins.
This is why extension can be a better choice than a custom skin - it is more flexible. But it also cannot affect default component styles which will certainly limit your options in some cases.
So in the end it is up to you to decide which approach to style customization you would use in your application - through a custom skin or through skin extensions.
Styles provide specific settings for the components they are written for. Component to which style is applied is defined by its type
and id
settings.
Default components styles have equal type
and id
. Default component styles are exactly what the name says - styles that are applied to all components of the specific type by default.
Here is a small example of how default style definition look like for label
:
<!-- Label -->
<style type="label">
...
</style>
Each styleable component created in runtime automatically gets default style like that one applied to it unless a different default or custom style is provided in the code.
To customize component's style you simply need to provide StyleId
instance referencing specific style into the component's constructor directly or use one of the alternative ways:
- Use
public StyleId setStyleId ( final StyleId id )
method in anyStyleable
component - Use
public StyleId set ( final JComponent component )
method inStyleId
instance - Use
public static StyleId setStyleId ( final JComponent component, final StyleId id )
inStyleManager
- Use one of other ways described here: How to style Swing components
All of these options are equal and will have exactly the same result. You are free to choose which way you prefer more or which way suits your use-case better.
Now, since styles currently have to be provided through XML - here is a small example from WebLightSkin
of a default label
style description:
<!-- Label -->
<style type="label">
<component>
<foreground>black</foreground>
</component>
<painter>
<decorations>
<decoration>
<LabelLayout>
<LabelIcon constraints="icon" />
<LabelText constraints="text" />
</LabelLayout>
</decoration>
<decoration states="disabled">
<LabelLayout>
<LabelText color="120,120,120" ignoreCustomColor="true" />
</LabelLayout>
</decoration>
</decorations>
</painter>
</style>
This part is taken from label.xml
file included in web-light-skin.xml
and represents style used for all JLabel
and WebLabel
components by default.
It might look overly complicated at first, but most components follow the very same structure of the style that is really easy to create and modify once you get used to it.
Each style can contain three main blocks:
-
<component>...</component>
Can contain any component class settings -
<ui>...</ui>
Can contain UI class settings -
<painter>...</painter>
Usually always there and contains painter settings
First two blocks are there to allow some basic customization of settings for Component and UI. For instance you can specify any general JComponent
settings like opaque
or enabled
in Component block. Although usually those two blocks are not needed as the most commonly used settings are already provided by default.
Painter block is the one you'll most probably be working the most when modifying the style of any component. It contains advanced settings for the component decoration and usually content as well.
Painters are the core element of new styling system - they provide all the painting code for each specific component and are highly customizable. They were initially designed to simplify some of the painting operations and provide developers with additional tools to replace painting code parts with custom ones, but they got heavily redesigned in the v1.2.9 update and now allow complete view customization for most components.
Each existing painter implements Painter
interface and in addition to that component specific painters also implement their own interfaces like ButtonPainter
or TabbedPanePainter
.
All painters are interface-based unlike the UIs which are class-based - ComponentUI
is an abstract class and all specific UIs like ButtonUI
are also classes, mostly empty ones. That fact allows having a single complex painting implementation for multiple visually similar components.
Currently the most used Painter
implementation is AbstractDecorationPainter
- it is designed for any kind of components and provides a flexible way to configure them. Of course it is an abstract class and actual implementations are classes like LabelPainter
and ButtonPainter
which often have no actual code difference.
Component painters that extend AbstractDecorationPainter
can contain <decorations>...</decorations>
block like the one in label
example above. That block can have any amount of <decoration>...</decoration>
sub-blocks for different states defined in it's states
attribute. Each of those sub-blocks can contain either full or partial description of a component view for the particular states specified in states
attribute of that sub-block.
So basically each style defines a number of decorations for different states it can have and must represent differently visually. Label is a pretty simple but good example because it doesn't have many visually-different states - only enabled
and disabled
.
So here is the last very important bit you must know. As you can see from two label
decoration states from example above - one state is fully described and doesn't have states
attribute:
<decoration>
<LabelLayout>
<LabelIcon constraints="icon" />
<LabelText constraints="text" color="black" />
</LabelLayout>
</decoration>
While the other one basically only defines text color and uses disabled
in states
:
<decoration states="disabled">
<LabelLayout>
<LabelText color="120,120,120" />
</LabelLayout>
</decoration>
The reason behind this is actually simple - information from all decoration blocks that match current component state get merged together and form the complete view. That is also why there is a decoration block that doesn't have any states - that is simply a default decoration that is used at all times, but can be overwritten by other block's settings.
Such merge of decorations simply removes necessity to define each separate component state from zero. So for example you can define all basic stuff in default state and then only modify some of the settings for other states. This is also how most currently implemented component styles are made.
Order of the merge is strict - blocks at higher indices will always have priority, so in the case of label
- disabled
state block will have priority over default block and will be merged on top of it. That is why when actual label gets disabled it's text will become gray with this style.
To bring structured styling system to life all skins and extensions are stored and managed within StyleManager
. It provides various methods to work with the skin contents, allowing you to style separate components and to listen to various style events.
Although StyleManager
doesn't really do much on its own - it's main job is to create and store StyleData
instances for each styleable component. StyleData
itself stores various runtime information about the component styling which is not coming from styles:
-
skin
-Skin
instance which is currently used for the particular component -
styleId
-StyleId
instance applied to that particular component -
customPainter
- Custom componentPainter
provided from the code -
children
- Style children linked throughStyleId
parent field -
listeners
-StyleListener
s for the component
All interactions with StyleData
are performed by StyleManager
itself and it shouldn't be accessed from outside to avoid any integrity issues.
Overall you probably won't use StyleManager
directly as often. Instead you will use various component and StyleId
methods to setup your component styles, while StyleManager
can mostly be used at startup to setup initial Skin
and SkinExtension
s and to change those later in runtime.
Now when you have a base knowledge about key elements involved in styling process its time to get the actual examples of how that knowledge could be applied.
As already mentioned before - you need to apply StyleId
to your components to modify their styling in runtime, otherwise they will be using the globally applied skin which is WebLightSkin
by default in WebLaF. So let's try and style some small group of components to see how it works.
First of all, this is what we have by default:
public class Example
{
public static void main ( final String[] args )
{
CoreSwingUtils.invokeLater ( new Runnable ()
{
@Override
public void run ()
{
WebLookAndFeel.install ();
final WebPanel panel = new WebPanel ( new VerticalFlowLayout ( true, true ) );
final WebLabel title = new WebLabel ( "Panel Title" );
panel.add ( title );
final WebSeparator separator = new WebSeparator ();
panel.add ( separator );
final WebTextArea textArea = new WebTextArea ( 3, 20 );
final WebScrollPane scrollPane = new WebScrollPane ( textArea );
panel.add ( scrollPane );
TestFrame.show ( panel );
}
} );
}
}
Just a note - I am using Web* components instead of J* components here for convenience. You can do all the same I will be doing in this example using different style setup methods mentioned above.
Here is the result:
Not really appealing, right? Let's adjust it using some predefined styles first:
public final class Example
{
public static void main ( final String[] args )
{
CoreSwingUtils.invokeLater ( new Runnable ()
{
@Override
public void run ()
{
WebLookAndFeel.install ();
final WebPanel panel = new WebPanel ( StyleId.panelDecorated, new VerticalFlowLayout ( true, true ) );
final WebLabel title = new WebLabel ( StyleId.labelShadow, "Panel Title" );
panel.add ( title );
final WebSeparator separator = new WebSeparator ( StyleId.separatorHorizontal );
panel.add ( separator );
final WebTextArea textArea = new WebTextArea ( 3, 20 );
final WebScrollPane scrollPane = new WebScrollPane ( StyleId.scrollpaneUndecorated, textArea );
panel.add ( scrollPane );
TestFrame.show ( panel );
}
} );
}
}
We used a few common styles available in the StyleId
- there are quite a few besides the default ones - you can use those safely as they are guaranteed to be present in any skin you are using.
Anyway, here is the result we get:
A bit better, but something is still not right - probably we want to have some spacing between the label and the panel/separator and the panel gradient background isn't really doing a good job here either. We can of course adjust some of the settings through the code - like padding - but it is generally not recommended to mix two different styling approaches together as they might interfere with each other in some cases. That is why let's write some custom styles and provide all necessary settings there.
First of all we need to create our skin, let's make it in ExampleSkin.xml
file:
<skin xmlns="http://weblookandfeel.com/XmlSkin">
<!-- Skin settings -->
<id>example.skin</id>
<class>com.alee.wiki.introduction.ExampleSkin</class>
<supportedSystems>all</supportedSystems>
<!-- Skin information -->
<title>Example skin</title>
<description>This is an example skin for WebLaF wiki</description>
<author>Mikle Garin</author>
<!-- Including WebLaF default skin, will use its style as a base -->
<include nearClass="com.alee.skin.light.WebLightSkin">resources/web-light-skin.xml</include>
</skin>
We are going to use default WebLightSkin
as a base for our own skin as it would take a lot of time to create completely new skin from a scratch, so we simply include it here.
We don't have any custom styles in it yet though, we'll make one a bit later - let's make sure everything works first.
We will need a custom skin class now:
public final class ExampleSkin extends XmlSkin
{
public ExampleSkin ()
{
super ( new ClassResource ( ExampleSkin.class, "ExampleSkin.xml" ) );
}
}
Note that it needs to be located near the ExampleSkin.xml
file for this exact example to work.
Now all we have left to do is to specify our custom skin class at the L&F install:
WebLookAndFeel.install ( ExampleSkin.class );
We can launch our example once again - nothing should change visually, but we're now using our own custom skin and can start customizing existing styles.
First of all we might want to do something about the panel style, so let's override default panel style by adding next style into our skin at the end:
<!-- Our custom panel style -->
<style type="panel">
<painter>
<decorations>
<decoration>
<WebShape round="5" />
<WebShadow type="outer" width="15" />
<LineBorder color="170,170,170" />
</decoration>
</decorations>
</painter>
</style>
And don't forget to remove our custom panel style usage:
final WebPanel panel = new WebPanel ( new VerticalFlowLayout ( true, true ) );
Great! Let's run this:
Something certainly went wrong.
That happened because we just applied the same decorated style for all panels in our application - content pane is a panel and TestFrame
uses another panel to store its content. This is why two additional panels with huge shade appeared.
That is why sometimes overriding default styles is not the best idea - you might affect some things that rely on those default styles which you might not want to change.
So, let's fix this by using a custom identifier for our panel style:
<!-- Our custom panel style -->
<style type="panel" id="shadow">
<painter>
<decorations>
<decoration>
<WebShape round="5" />
<WebShadow type="outer" width="15" />
<LineBorder color="170,170,170" />
</decoration>
</decorations>
</painter>
</style>
And we must also reference it properly in the panel now which we also forgot to do before that:
final WebPanel panel = new WebPanel ( StyleId.of ( "shadow" ), new VerticalFlowLayout ( true, true ) );
And here is the desired result:
We should be done with the panel now, so let's move to the other parts. I'll skip all similar steps I've explained for the panel to be short, so here are the resulting styles for all UI elements we have:
<!-- Our custom panel style -->
<style type="panel" id="shadow">
<painter>
<decorations>
<decoration>
<WebShape round="5" />
<WebShadow type="outer" width="15" />
<LineBorder color="170,170,170" />
</decoration>
</decorations>
</painter>
</style>
<!-- Title label style -->
<style type="label" id="title" extends="shadow" padding="5,7,5,7" />
<!-- Separator style -->
<style type="separator" id="line" extends="horizontal" />
<!-- Scroll pane style -->
<style type="scrollpane" id="scroll" extends="transparent" padding="5,5,5,5" />
<!-- Text area style -->
<style type="textarea" id="text">
<component>
<opaque>false</opaque>
</component>
</style>
And the code which references those styles:
public final class Example
{
public static void main ( final String[] args )
{
CoreSwingUtils.invokeLater ( new Runnable ()
{
@Override
public void run ()
{
WebLookAndFeel.install ( ExampleSkin.class );
final WebPanel panel = new WebPanel ( StyleId.of ( "shadow" ), new VerticalFlowLayout ( true, true ) );
final WebLabel title = new WebLabel ( StyleId.of ( "title" ), "Panel Title" );
panel.add ( title );
final WebSeparator separator = new WebSeparator ( StyleId.of ( "line" ) );
panel.add ( separator );
final WebTextArea textArea = new WebTextArea ( StyleId.of ( "text" ), 3, 20 );
final WebScrollPane scrollPane = new WebScrollPane ( StyleId.of ( "scroll" ), textArea );
scrollPane.setHorizontalScrollBarPolicy ( ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER );
panel.add ( scrollPane );
TestFrame.show ( panel );
}
} );
}
}
I have added a small fix for the scroll here as well to hide unnecessary horizontal scroll bar.
And this is the resulting view:
Overall better, but scroll still looks awkward.
Also our style and UI code looks messy with all those hardcoded styles - we should do something about it. Let's structure our style so that it actually reflects the structure of our panel:
<!-- Our custom panel style -->
<style type="panel" id="shadow">
<painter>
<decorations>
<decoration>
<WebShape round="5" />
<WebShadow type="outer" width="15" />
<LineBorder color="170,170,170" />
</decoration>
</decorations>
</painter>
<!-- Title label style -->
<style type="label" id="title" extends="shadow" padding="5,7,5,7" />
<!-- Separator style -->
<style type="separator" id="line" extends="horizontal" />
<!-- Scroll pane style -->
<style type="scrollpane" id="scroll" extends="transparent" padding="5,5,5,5">
<!-- Text area style -->
<style type="textarea" id="text">
<component>
<opaque>false</opaque>
</component>
</style>
</style>
</style>
It looks way better now and it also shows how components are placed within the UI.
Now let's have a look at the changes in the code I had to make to apply these styles:
public final class Example
{
public static void main ( final String[] args )
{
CoreSwingUtils.invokeLater ( new Runnable ()
{
@Override
public void run ()
{
WebLookAndFeel.install ( ExampleSkin.class );
final WebPanel panel = new WebPanel ( StyleId.of ( "shadow" ), new VerticalFlowLayout ( true, true ) );
final WebLabel title = new WebLabel ( StyleId.of ( "title", panel ), "Panel Title" );
panel.add ( title );
final WebSeparator separator = new WebSeparator ( StyleId.of ( "line", panel ) );
panel.add ( separator );
final WebScrollPane scrollPane = new WebScrollPane ( StyleId.of ( "scroll", panel ) );
scrollPane.setHorizontalScrollBarPolicy ( ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER );
scrollPane.getViewport ().setView ( new WebTextArea ( StyleId.of ( "text", scrollPane ), 3, 20 ) );
panel.add ( scrollPane );
TestFrame.show ( panel );
}
} );
}
}
The main change here - I had to provide parent into StyleId
to properly link styles according to their structure in XML. Note that you don't need to fully reflect components placement in the styles, you don't actually need to reflect it at all. I only recommend grouping styles like this when it is possible for maintainability reasons. On practice it is sometimes hard to reach out for the parent to setup styles like that and might not be needed at all, it all depends on the use case in the end.
Now, let's also add last few changes to the code - we still didn't do anything about hardcoded style references. Over time I came up with a pretty simple solution which might not look too good, but it certainly helps you to avoid problems and gather up style definitions in one place - simply make a class or interface where you would store all your StyleId
s referencing your custom styles:
public final class ExampleStyles
{
public static final StyleId shaded = StyleId.of ( "shadow" );
public static final ChildStyleId title = ChildStyleId.of ( "title" );
public static final ChildStyleId line = ChildStyleId.of ( "line" );
public static final ChildStyleId scroll = ChildStyleId.of ( "scroll" );
public static final ChildStyleId text = ChildStyleId.of ( "text" );
}
We have moved all the styles we had into a separate class and can use them anywhere in our application now by simply referencing this class constants.
Also you might notice that all parent-related styles are referenced through ChildStyleId
- it is a convenient utility class that allows dynamic StyleId
instance creation for a particular parent on the spot. Check this example to see how it's used:
public final class Example
{
public static void main ( final String[] args )
{
CoreSwingUtils.invokeLater ( new Runnable ()
{
@Override
public void run ()
{
WebLookAndFeel.install ( ExampleSkin.class );
final WebPanel panel = new WebPanel ( ExampleStyles.shadow, new VerticalFlowLayout ( true, true ) );
final WebLabel title = new WebLabel ( ExampleStyles.title.at ( panel ), "Panel Title" );
panel.add ( title );
final WebSeparator separator = new WebSeparator ( ExampleStyles.line.at ( panel ) );
panel.add ( separator );
final WebScrollPane scrollPane = new WebScrollPane ( ExampleStyles.scroll.at ( panel ) );
scrollPane.setHorizontalScrollBarPolicy ( ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER );
scrollPane.getViewport ().setView ( new WebTextArea ( ExampleStyles.text.at ( scrollPane ), 3, 20 ) );
panel.add ( scrollPane );
TestFrame.show ( panel );
}
} );
}
}
Better than before, right? And it is still look exactly like before:
Let's adjust the gray background around the text area and scroll style.
I should have probably used two separate panels for the top and bottom sides and simply style the bottom panel to have white background, but sometimes it might be hard to change the structure of your UI, so we will work with what we have:
<!-- Our custom panel style -->
<style type="panel" id="shadow">
<painter>
<decorations>
<decoration>
<WebShape round="5" />
<WebShadow type="outer" width="15" />
<LineBorder color="170,170,170" />
</decoration>
</decorations>
</painter>
<!-- Title label style -->
<style type="label" id="title" extends="shadow" padding="5,7,5,7" />
<!-- Separator style -->
<style type="separator" id="line" extends="horizontal" />
<!-- Scroll pane style -->
<style type="scrollpane" id="scroll" extends="transparent-hovering" padding="5,5,5,5">
<painter>
<decorations overwrite="true">
<decoration>
<WebShape round="0,0,5,5" />
<ColorBackground color="white" />
</decoration>
</decorations>
</painter>
<!-- Text area style -->
<style type="textarea" id="text">
<component>
<opaque>false</opaque>
</component>
</style>
</style>
</style>
I have made a small trick there - colored the scrollpane background to white and made it fit the rounding of the panel.
And we get exactly the result we wanted:
And here is how our complete skin look like now:
<skin xmlns="http://weblookandfeel.com/XmlSkin">
<!-- Skin settings -->
<id>example.skin</id>
<class>com.alee.wiki.introduction.ExampleSkin</class>
<supportedSystems>all</supportedSystems>
<!-- Skin information -->
<title>Example skin</title>
<description>This is an example skin for WebLaF wiki</description>
<author>Mikle Garin</author>
<!-- Including WebLaF default skin, will use its style as a base -->
<include nearClass="com.alee.skin.light.WebLightSkin">resources/web-light-skin.xml</include>
<!-- Our custom panel style -->
<style type="panel" id="shadow">
<painter>
<decorations>
<decoration>
<WebShape round="5" />
<WebShadow type="outer" width="15" />
<LineBorder color="170,170,170" />
</decoration>
</decorations>
</painter>
<!-- Title label style -->
<style type="label" id="title" extends="shadow" padding="5,7,5,7" />
<!-- Separator style -->
<style type="separator" id="line" extends="horizontal" />
<!-- Scroll pane style -->
<style type="scrollpane" id="scroll" extends="transparent-hovering" padding="5,5,5,5">
<painter>
<decorations overwrite="true">
<decoration>
<WebShape round="0,0,5,5" />
<ColorBackground color="white" />
</decoration>
</decorations>
</painter>
<!-- Text area style -->
<style type="textarea" id="text">
<component>
<opaque>false</opaque>
</component>
</style>
</style>
</style>
</skin>
We've done quite a few adjustments along the way and spent quite some time doing them, but we also ended up with a way more visually-appealing result. Whether it was worth the time or not always depends on what your goal is.
We already made a custom skin with a custom style for our panel, but it depends on WebLightSkin
. So what if we want to create a dark variation? Well, we can start with creating another custom skin with dark style variation for our panel.
Let's create new ExampleDarkSkin.xml
:
<skin xmlns="http://weblookandfeel.com/XmlSkin">
<!-- Skin settings -->
<id>example.dark.skin</id>
<class>com.alee.wiki.introduction.ExampleDarkSkin</class>
<supportedSystems>all</supportedSystems>
<!-- Skin information -->
<title>Example Dark skin</title>
<description>This is an example dark skin for WebLaF wiki</description>
<author>Mikle Garin</author>
<!-- Including WebLaF default skin, will use its style as a base -->
<include nearClass="com.alee.skin.dark.WebDarkSkin">resources/web-dark-skin.xml</include>
<!-- Our custom panel style -->
<style type="panel" id="shadow">
<painter>
<decorations>
<decoration>
<WebShape round="5" />
<WebShadow type="outer" width="15" />
<LineBorder color="20,20,20" />
</decoration>
</decorations>
</painter>
<!-- Title label style -->
<style type="label" id="title" extends="shadow" padding="5,7,5,7" />
<!-- Separator style -->
<style type="separator" id="line" extends="horizontal" />
<!-- Scroll pane style -->
<style type="scrollpane" id="scroll" extends="transparent-hovering" padding="5,5,5,5">
<painter>
<decorations overwrite="true">
<decoration>
<WebShape round="0,0,5,5" />
<ColorBackground color="106,110,112" />
</decoration>
</decorations>
</painter>
<!-- Text area style -->
<style type="textarea" id="text">
<component>
<opaque>false</opaque>
</component>
</style>
</style>
</style>
</skin>
We will also need new custom skin class - ExampleDarkSkin.java
:
public final class ExampleDarkSkin extends XmlSkin
{
public ExampleDarkSkin ()
{
super ( ExampleDarkSkin.class, "ExampleDarkSkin.xml" );
}
}
Nothing new, exactly what we did for the previous example.
Now we just need to replace the skin we load in our example:
WebLookAndFeel.install ( ExampleDarkSkin.class );
and we can see the dark version of our panel:
Pretty simple and straightforward.
Now we can also add some way to switch between these two styles in runtime if we want to:
public final class Example
{
public static void main ( final String[] args )
{
CoreSwingUtils.invokeLater ( new Runnable ()
{
@Override
public void run ()
{
WebLookAndFeel.install ( ExampleDarkSkin.class );
final Skin dark = StyleManager.getSkin ();
final Skin light = new ExampleSkin ();
final WebPanel panel = new WebPanel ( ExampleStyles.shadow, new VerticalFlowLayout ( true, true ) );
final WebLabel title = new WebLabel ( ExampleStyles.title.at ( panel ), "Panel Title" );
panel.add ( title );
final WebSeparator separator = new WebSeparator ( ExampleStyles.line.at ( panel ) );
panel.add ( separator );
final WebScrollPane scrollPane = new WebScrollPane ( ExampleStyles.scroll.at ( panel ) );
scrollPane.setHorizontalScrollBarPolicy ( ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER );
scrollPane.getViewport ().setView ( new WebTextArea ( ExampleStyles.text.at ( scrollPane ), 3, 20 ) );
panel.add ( scrollPane );
TestFrame.show ( panel );
HotkeyManager.registerHotkey ( Hotkey.CTRL_S, new HotkeyRunnable ()
{
@Override
public void run ( final KeyEvent e )
{
StyleManager.setSkin ( StyleManager.getSkin () == dark ? light : dark );
}
} );
}
} );
}
}
And now we can switch the skin using CTRL+S
hotkey:
First initialization of the second (light) skin might take a bit of time first time because it doesn't automatically initialize all styles to save startup time. But once skin is initialized - changing to it should only take a few milliseconds and would most probably look instant to the application user.
One last topic I didn't touch here yet - can you actually use multiply skins at once? - The answer is YES, you certainly can do that. Let's try applying the light skin to a part of our panel example - the scrollpane:
final WebScrollPane scrollPane = new WebScrollPane ( ExampleStyles.scroll.at ( panel ) );
scrollPane.setHorizontalScrollBarPolicy ( ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER );
scrollPane.getViewport ().setView ( new WebTextArea ( ExampleStyles.text.at ( scrollPane ), 3, 20 ) );
scrollPane.setSkin ( light );
panel.add ( scrollPane );
Yes, it's that simple - just pass the custom skin into the component and it will automatically be applied to it and all other components referencing this one as parent through the styles.
This is how our example will look like now:
You can ensure that the black part skin is pinned to light one and isn't changing upon the global skin change by simply switching global skin back and forth a few times.
Now when you are almost ready to start styling your application you might ask one last question:
Where should I find all the settings available for each component, UI and painter?
And the answer is simple - look and the source code of the appropriate component, UI or painter class. This is the most reliable way to find out.
Every single tag or attribute available in the XML represents an actual class or field. For instance <decorations>
and <decoration>
are represented by Decorations
and WebDecoration
classes.
As an alternative option - you can look at the default skin XML files which already contain styling for all existing components. Those are good examples of what settings you can use for each component.
This is it, you should know enough by now to start creating your own styles and skins using WebLaF!
Contact me anytime if something is still missing or feels incomplete - I will update article with more useful information to cover up the blank spots.
If you found any mistakes or inconsistency in this article, feel that it is lacking explanation or simply want to request an additional wiki article covering some topic:
I will do my best to answer and provide assistance as soon as possible!