-
Notifications
You must be signed in to change notification settings - Fork 235
How to use StyleManager
As you might know, WebLaF v1.27 update brings a brand new StyleManager live, but I must say that its current version is still not completed and have to be polished in next few updates. Most of its features are already finished and working as intended but I am sure that as soon as people start using this new version some weird bugs will certainly appear.
Also current StyleManager is just a preview version that affects only a few widely used components like JLabel
JPanel
JScrollBar
and JPopupMenu
. It's hard to explain why i started with these ones so i'll put this topic aside for now.
I didn't publish it earlier just because I had to refactor tremendous amounts of code to implement StyleManager without breaking lots of other stuff. The rest of the work is just making proper UIs and Painters for each Swing and WebLaF component - believe me, that is a lot easier.
So this WIKI page will be updated once more as soon as StyleManager feature released.
I will start from the beginning so you can actually understand how does StyleManager work and how you can use it to your advantage.
As you might know, almost every Swing component has an appropriate J-component (for example JButton), an UI class (for example MetalButtonUI) and some of them also have a model (for example ButtonModel, TreeModel, TableModel and others). Basically something close to MVC pattern. But what i don't really like is that some of the components are really messed up, especially their UI parts.
So what I actually did there - I have separated all the painting operations from the UI classes and placed it into painters. Such painters became component-specific and recieved some additional methods according to component they are used in. So you might now find interfaces like LabelPainter, PanelPainter and others which have special methods to interact with component UI to paint that component properly. Those are the base painter interfaces for Swing and WebLaF components. Here is an example of LabelPainter:
public interface LabelPainter<E extends JLabel> extends Painter<E>, SpecificPainter
{
/**
* Sets whether text shade should be displayed or not.
*
* @param drawShade whether text shade should be displayed or not
*/
public void setDrawShade ( final boolean drawShade );
}
It is simple and includes only one additional method that should be implemented in any JLabel painter. Some interfaces may contain more methods, that is usually affected by the UI:
public interface ScrollBarPainter<E extends JScrollBar> extends Painter<E>, SpecificPainter
{
/**
* Sets whether scroll bar arrow buttons are visible or not.
* Buttons are painted separately, this mark simply informs whether they are actually visible or not.
*
* @param paint whether scroll bar arrow buttons are visible or not
*/
public void setPaintButtons ( boolean paint );
/**
* Sets whether scroll bar track should be painted or not.
*
* @param paint whether scroll bar track should be painted or not
*/
public void setPaintTrack ( boolean paint );
/**
* Sets whether scroll bar thumb is being dragged or not.
* This value is updated by WebScrollBarUI when drag event starts or ends.
*
* @param dragged whether scroll bar thumb is being dragged or not
*/
public void setDragged ( boolean dragged );
/**
* Sets scroll bar track bounds.
* This value is updated by WebScrollBarUI on each paint call to ensure that proper bounds presented.
*
* @param bounds new scroll bar track bounds
*/
public void setTrackBounds ( Rectangle bounds );
/**
* Sets scroll bar thumb bounds.
* This value is updated by WebScrollBarUI on each paint call to ensure that proper bounds presented.
*
* @param bounds new scroll bar thumb bounds
*/
public void setThumbBounds ( Rectangle bounds );
}
I have also made a lot of improvements to Painter interface as it is the core feature of the WebLaF. You are now able to call various component updates (repaint, revalidate and full update) directly from the painter. Until now you had to update component externally in case you had some unexpected view changes (for example in case of animation) but now you can keep all the painting-related stuff right inside your Painter class. I also recommend using AbstractPainter (or any component-specific painter) to achieve your goals instead of writing a painter on your own from a scratch since it includes a set of useful methods described above.
To sum up
The main thing you should remember from all the stuff described above that WebLaF uses three component layers: J-component
component-UI
and component-Painter
. Note that model is out of scope here since it doesn't affect the view of the component and not presented in some of the components at all.
Each of these layers should be properly configured (sometimes in a strict order) and StyleManager helps you to achieve that without knowing a lot about Swing.
So painters are the core feature StyleManager relies on but it is not the only one. Let's continue to the manager itself and some newly introduced stuff.
After looking at a dozen of L&Fs that supports some predefined skins (aka "themes", but I actually prefer "skin") or at least pretend to do so - I realized that it is not what I need and so I made my own skins support from a scratch. It took some time but it totally worth it!
So what basically WebLafSkin is about? It is an object that can provide predefined painters and settings for supported components. Settings are separated into three groups I have mentioned before: component-settings, ui-settings and painter-settings. It is not that simple from inside of course but its the main role of WebLafSkin aside from other features it has.
To be exact WebLafSkin not just provides some painter and settings, it can set it into the component on its own without StyleManager assistance. Basically if you have an instance of WebLafSkin you can manually style any component by simply calling skin.applySkin ( component )
. That will create painters for the component, apply all the settings skin has and will update everything that has to be updated.
StyleManager knows nothing about styling itself and only refers to specific skin when something has to be styled. It also has its own important role - it keeps a track of currently used skin and all customizations made to the default skin settings and painters in separate components. Without it each skin change would force your interface to "forget" what you have manually modified in component settings (for example margins, custom colors or painters).
I will mention StyleManager a bit later again in specific examples so let's move on.
WebLafSkin is an abstract class and has a few methods that should be overridden and CustomSkin is its basic extension. CustomSkin offers a way to load skin settings from special XML skin files.
Here we come to some more specific topic - skin XML format. I will explain how it works and how is stores information about painters and various components settings.
Here is the base WebSkin XML file - WebSkin.xml
:
<skin>
<!-- Skin information (not necessary) -->
<id>weblaf.default.skin</id>
<name>WebLaF default skin</name>
<description>Default WebLaF interface skin</description>
<author>Mikle Garin</author>
<supportedSystems>all</supportedSystems>
<!-- Base skin class, included resources with no specified "nearClass" attribute will use this class -->
<class>com.alee.managers.style.skin.web.WebSkin</class>
<!-- Separate style classes -->
<include>resources/label.xml</include>
<include>resources/panel.xml</include>
<include>resources/scrollBar.xml</include>
<include>resources/popupMenu.xml</include>
</skin>
It is short because it only includes separate style files to gather them into a single complete skin. Each separate style is basically the same skin object but without information about the skin itself. It might include one but it will be ignored if that won't be the first skin loaded in the chain.
Element <skin>
refers to SkinInfo
object. Some additional information like included styles presented only in XML but it is resolved while deserialization. Deserialization process itself is pretty complicated and done in a few steps, though it usually takes only a few milliseconds to complete and it is done only once when skin information is being loaded so it doesn't affect performance. You can try launching StyleEditor
class to see how fast style updates are.
Elements <id>
<name>
<description>
and <author>
are simply skin description. Though your skin <id>
must be unique within single running application.
Element <supportedSystems>
specifies actual systems supported by this skin. You can limit supported OS by specifying them like this:
<supportedSystems>win,mac</supportedSystems>
Possible options: win
mac
unix
solaris
and all
.
Element <class>
may be specified to provide skin-wide path to resources and to point skin's main class, though it is not required field. But if it is not specified you should add nearClass
attribute to each separate <include>
element.
Element <include>
simply refers to another skin that should be used by this one. You can even make such included skin include other skins - that will also work. Just make sure you don't make an infinite circle of includes as it is not checked so far and will cause an exception.
And now let's look at specific component style WebSkin.xml
includes - label.xml
:
<skin>
<!-- JLabel styling -->
<style type="label" id="default">
<component>
<foreground>black</foreground>
<background>237,237,237</background>
</component>
<ui>
<drawShade>false</drawShade>
</ui>
<painter id="painter" class="com.alee.managers.style.skin.web.WebLabelPainter">
<shadeColor>200,200,200</shadeColor>
</painter>
</style>
<!-- WebVerticalLabel styling -->
<style type="verticalLabel" id="default">
<component>
<foreground>black</foreground>
<background>237,237,237</background>
</component>
<painter id="painter" class="com.alee.managers.style.skin.web.WebVerticalLabelPainter">
<drawShade>false</drawShade>
<shadeColor>200,200,200</shadeColor>
</painter>
</style>
<!-- WebCustomTooltip label styling -->
<style type="label" id="custom-tooltip-label" extends="default">
<component>
<foreground>white</foreground>
</component>
</style>
</skin>
I have shortened the label.xml
on purpose, but it is still more than enough to explain how things work. You can find complete skin files list including label.xml
here: https://github.com/mgarin/weblaf/tree/master/src/com/alee/managers/style/skin/web/resources
So, you may notice that this one contains some new elements...
Element <style>
refers to ComponentStyle
object and represents a single style that contains component, ui and painter settings in it. Those settings are basically raw values of variables in the appropriate J-component, component-UI and component-painter classes. WebLafSkin apply those settings through reflection using appropriate setter methods if possible so you don't actually have to worry about adding any support for it - you can add new variable into your component and then just provide its value in the XML and it will be applied properly when that skin is installed. It also has a few additional attributes:
-
type
attribute refers toSupportedComponent
enumeration -
id
attribute represents unique for this skin style ID -
extends
attribute refers to another style ID which should be extended by this one
Note that if id
is set to default
(like in the first style in the example above) such style will be considered as the default style for the specified component type and will be applied to all components of that type which doesn't separately specify their style ID.
And yes, you can actually extend styles by specifying style ID in the extends
attribute. In case some style B extends another style A - A's component and UI settings will be applied to B style, A's painter settings will be applied to style B in case there is no painter specified in B or B's painter can be cast to A's painter - in other cases A's painter settings will simply be ignored, all settings provided in B style will have priority over A's settings.
Element <painter>
contains painter settings and also has a few additional attributes:
-
id
attribute represents unique for this style painter ID and painter UI variable -
base
attribute determines whether this is a base painter for this style or not -
painterClass
attribute represents painter class canonical name
As said above id
attribute should fit painter's variable name in the UI so it can be used to set that painter into the UI properly.
So here is how base structure of a style XML looks like:
<skin>
<style type="<componentType>" id="default">
<component>
<!-- Component settings here -->
</component>
<ui>
<!-- UI settings here -->
</ui>
<painter id="painter" class="<painterClass>">
<!-- Painter settings here -->
</painter>
</style>
</skin>
You can exclude any part of the style you want. Making an empty style makes no sense though but it is allowed.
I want to make a few notes about how some specific styling parts work...
Component, UI and painter settings and painter itself provided by the specific skin have strict installation priority:
- Painter created using the class provided by skin
- Painter is configured using painter settings provided by skin
- Painter is installed into the component UI
- UI settings provided by skin are applied
- Component settings provided by skin are applied
Why is this so important? Because sometimes same settings might be found in both UI and painter - in that case UI settings will override painter settings and you should consider that when creating your own skin.
So what actually happens when skin is installed instead of changing the whole Look and Feel? I guess this is also easier to explain by providing the steps WebLaF goes through:
- Skin is checked whether it is supported on current OS or not
- Skin is saved as current skin in StyleManager to be used by newly created components
- Skin is applied to all components that were registered as "styled" in StyleManager
The core idea about skin is that it only controls visual component representation and switches component's painter keeping its UI and avoiding heavy update operations. That is why I had to fully separate painter from UI.
Since skin settings are mostly about specific component/ui/painter properties I want to explain how exactly they work. First of all - yes, all properties are set into component/ui/painter instance using Reflection API within the WebLafSkin
class.
As you might have noticed properties do not specify their value type, they are determined when skin XML file is being read. StyleManager simply looks for fields with the same name as the property inside component/ui/painter class (or its superclasses, which is important) and uses its type to read value properly from skin XML file. You can see how this whole thing works by simply looking into the ComponentStyleConverter
class - at unmarshal
method.
Sometimes you might want to avoid skin affecting some of the component/ui/painter properties, but what if they are already set in the extended style and you don't want to create a fully new style? Pretty simple - you have to add an ignored mark into the properties you don't want to apply:
<style type="panel" id="ignored-background" extends="default">
<component>
<background ignored="true" />
</component>
</style>
This specific example will force any panel that uses ignored-background
style ID to use default background set in the default painter (or even in the L&F) for panel instead of the one provided by default panel skin.
This feature might also be useful in case you have some class that extends some styleable component and overrides some fields into something totally different. Here is a short example:
public class PanelExample extends WebPanel
{
private String background = "something";
public static void main ( String[] args )
{
WebLookAndFeel.install ();
TestFrame.show ( new PanelExample () );
}
}
Running this example will throw an IllegalArgumentException
saying something like:
Can not set java.lang.String field test.PanelExample.background to java.awt.Color
That happened because WebLaF tried to set new Color into background
field while styling your custom component which isn't any different from simple panel, but has a different background
field type. This is exactly the case when you want to add an ignore mark to your customized field so that WebLaF will simply skip it.
So what that update actually brings to the developer who uses WebLaF?
First and main thing that you can now easily create complete and separate component stylings to use them anywhere in your application. That allows you to avoid creating code duplicates all over the code and lowers the chance to have some style-related issues.
Here is how you can start your own style:
<skin>
<!-- Skin information (not necessary) -->
<id>my.project.skin</id>
<name>My Project skin</name>
<description>This is just an example skin</description>
<author>Mikle Garin</author>
<supportedSystems>all</supportedSystems>
<!-- Your own skin class -->
<class>my.project.MySkin</class>
<!-- Including WebLaF default skin, will use its style as a base -->
<include nearClass="com.alee.managers.style.skin.web.WebSkin">resources/WebSkin.xml</include>
<!-- Custom panel styling with inner shade -->
<style type="panel" id="inner-shade">
<painter id="painter" class="com.alee.extended.painter.InnerShadePainter">
<shadeWidth>10</shadeWidth>
<round>2</round>
<shadeOpacity>0.75</shadeOpacity>
</painter>
</style>
</skin>
As you have noticed this custom skin has an additional style for JPanel component. We just need to create the skin class now (near which skin XML is located):
public class MySkin extends CustomSkin
{
public MySkin ()
{
super ( "resources/MySkin.xml" );
}
}
And we can use it in our application:
public class SkinTest
{
public static void main ( final String[] args )
{
// Custom skin for WebLaF
StyleManager.setDefaultSkin ( MySkin.class.getCanonicalName () );
WebLookAndFeel.install ();
// Styled panel
final WebPanel panel = new WebPanel ( "inner-shade" );
panel.add ( new WebLabel ( "Simple content" ) );
TestFrame.show ( panel, 50 );
}
}
And the result:
That simple!
And of course you are free to create separate skin XML files, include them, play with styles, use your custom painters - anything you like :)
By the way, skin can also be installed in runtime:
// Any of these works
StyleManager.applySkin ( MySkin.class.getCanonicalName () );
StyleManager.applySkin ( "my.project.MySkin" );
StyleManager.applySkin ( new MySkin () );
But I recommend changing the default skin at the start-up to save some processing time and exclude pointless memory operations with default skin.
There were actually two things i was trying to achieve with this large improvement:
- Fully separate component's representation so it can be easily modified
- Separate component's styling from the application code into skin or even into skin's XML
I think that both goals were achieved but StyleManager still requires some improvements till the final release. I hope it will not fail your expectations and will actually become a powerful tool!
I will be adding more information with next updates based on the feedback, so I am eager to hear what you think about it and ready to fix any issues you might find :)
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!